<?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>Forem: Ali Suleyman TOPUZ</title>
    <description>The latest articles on Forem by Ali Suleyman TOPUZ (@topuzas).</description>
    <link>https://forem.com/topuzas</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F853398%2Ff4651553-a23a-4bb6-8a12-a41a46317641.jpeg</url>
      <title>Forem: Ali Suleyman TOPUZ</title>
      <link>https://forem.com/topuzas</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/topuzas"/>
    <language>en</language>
    <item>
      <title>Building an Eval Stack for a LangGraph Agent: From LangFuse to AWS AgentCore</title>
      <dc:creator>Ali Suleyman TOPUZ</dc:creator>
      <pubDate>Sat, 11 Apr 2026 21:17:26 +0000</pubDate>
      <link>https://forem.com/topuzas/building-an-eval-stack-for-a-langgraph-agent-from-langfuse-to-aws-agentcore-8id</link>
      <guid>https://forem.com/topuzas/building-an-eval-stack-for-a-langgraph-agent-from-langfuse-to-aws-agentcore-8id</guid>
      <description>&lt;p&gt;&lt;em&gt;How a two-week evaluation design sprint almost ended with us switching tools entirely — and what we learned from not doing that.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There’s a particular kind of confidence that sneaks up on you when you’re building an LLM agent. You test it manually a few times, it gives reasonable answers, and you think: &lt;em&gt;okay, this works&lt;/em&gt;. Then someone on the team asks, “but how do you know it works?” and suddenly that confidence gets a lot wobblier.&lt;/p&gt;

&lt;p&gt;That question is what led us down the rabbit hole of LLM evaluation infrastructure for our agent system — a multi-layer, tool-heavy setup built on LangGraph, running in AWS Bedrock AgentCore, with a FastMCP server handling tool calls. We had three distinct layers — conversation, orchestration, and search — and each of them could fail in different, non-obvious ways. “It works” wasn’t good enough. We needed proof.&lt;/p&gt;

&lt;p&gt;This is the story of how we built the eval stack, nearly replaced it entirely, and ended up with a decision framework that I think applies well beyond our specific setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Problem: What Does “Eval” Even Mean for an Agent?
&lt;/h3&gt;

&lt;p&gt;Here’s something that doesn’t get said enough: &lt;strong&gt;evaluation for an LLM agent is not one thing&lt;/strong&gt;. It’s at least three.&lt;/p&gt;

&lt;p&gt;In a traditional software system, you write unit tests for functions, integration tests for services, and end-to-end tests for flows. An agent has the same stratification — except that the “functions” are probabilistic, the “services” are external model APIs, and the “flows” involve multi-turn conversations with context that mutates across turns.&lt;/p&gt;

&lt;p&gt;For our LangGraph-based agent, we identified three evaluation concerns that had to be addressed independently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Conversation quality&lt;/strong&gt;  — Is the final response accurate? Is it grounded in the retrieved context? Is it relevant to what the user actually asked?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orchestration quality&lt;/strong&gt;  — Is the agent routing to the right tools? Is it invoking them with correct parameters? Is it retrying sensibly when something goes wrong?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search/retrieval quality&lt;/strong&gt;  — When a RAG-like tool call happens, is the context that comes back actually useful? Is the retrieved content faithful to the source?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A monolithic evaluator that just looks at the final output misses everything in the middle. You can have a response that looks good but was assembled from hallucinated intermediate steps, or a retrieval call that returned garbage that the LLM happened to paper over with prior knowledge. You won’t catch either of those without layer-specific evals.&lt;/p&gt;

&lt;p&gt;This is the core reason why we needed a structured approach rather than ad-hoc testing.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The Test Fixtures: .eval.yaml
&lt;/h3&gt;

&lt;p&gt;Before picking any tools, we needed a consistent format for defining what we were testing. We settled on a pattern tied to a ticketing approach we internally called a simple YAML structure with two required fields:&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="c1"&gt;# example: booking_intent.eval.yaml&lt;/span&gt;

&lt;span class="na"&gt;test_input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;conversation_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;test-001"&lt;/span&gt;
  &lt;span class="na"&gt;user_message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;I&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;need&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;extend&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;my&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;stay&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;by&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;two&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;nights,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;checking&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;out&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;on&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Friday&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;instead&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;of&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Wednesday."&lt;/span&gt;
  &lt;span class="na"&gt;session_context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;property_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prop_42"&lt;/span&gt;
    &lt;span class="na"&gt;current_checkout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2024-11-20"&lt;/span&gt;

&lt;span class="na"&gt;success_criteria&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;contains_tool_call&lt;/span&gt;
    &lt;span class="na"&gt;tool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;modify_reservation"&lt;/span&gt;
    &lt;span class="na"&gt;with_params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;new_checkout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2024-11-22"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;llm_judge&lt;/span&gt;
    &lt;span class="na"&gt;metric&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;response_relevance&lt;/span&gt;
    &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.85&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deterministic&lt;/span&gt;
    &lt;span class="na"&gt;check&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;no_hallucinated_dates&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test_input captures a realistic scenario — not a synthetic toy, but something derived from actual usage patterns or edge cases that were reported. The success_criteria is a mixed list of deterministic checks (did the right tool get called with the right parameters?) and LLM-judged metrics (is the response relevant, faithful, grounded?).&lt;/p&gt;

&lt;p&gt;Why separate YAML files per test case rather than a big test suite file? A few reasons: they’re easier to review in PRs, they can be tagged and filtered independently, and they map cleanly to the tickets or stories that motivated them. When a new edge case surfaces in production, you create one new file and the eval pipeline picks it up automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. First Proposal: LangFuse + Ragas + DeepEval
&lt;/h3&gt;

&lt;p&gt;Our initial evaluation stack combined three tools, each with a distinct role:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LangFuse&lt;/strong&gt; handles tracing and observability. Every LangGraph node execution gets captured — what went in, what came out, how long it took, what the token counts looked like. It’s the backbone that gives you visibility into what the agent actually did, not just what it said.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ragas&lt;/strong&gt; provides the core RAG-oriented metrics. Faithfulness (is the response supported by the retrieved context?), answer relevance (does the answer actually address the question?), context precision, context recall. These are the metrics that matter for retrieval-augmented flows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DeepEval&lt;/strong&gt; fills in the rest — hallucination detection, task-specific metrics, toxicity checking, and the ability to define custom metrics with your own rubrics. It also provides the test runner infrastructure that ties everything together.&lt;/p&gt;

&lt;h4&gt;
  
  
  Docker Compose setup to Get This Running (Local)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.9"&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;langfuse&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;langfuse/langfuse:latest&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL=postgresql://langfuse:langfuse@postgres:5432/langfuse&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NEXTAUTH_SECRET=your-secret-here&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NEXTAUTH_URL=http://localhost:3000&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SALT=your-salt-here&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;

  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:15&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;langfuse&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;langfuse&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;langfuse&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pgdata:/var/lib/postgresql/data&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pgdata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Python-side wiring looks like as follows:&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;# eval_runner.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&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;langfuse.callback&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CallbackHandler&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;evaluate&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval.metrics&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;AnswerRelevancyMetric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;FaithfulnessMetric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;HallucinationMetric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;deepeval.test_case&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LLMTestCase&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;ragas.metrics&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;faithfulness&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;answer_relevancy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context_precision&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;ragas&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;evaluate&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ragas_evaluate&lt;/span&gt;

&lt;span class="n"&gt;LANGFUSE_HANDLER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CallbackHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;public_key&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;LANGFUSE_PUBLIC_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;secret_key&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;LANGFUSE_SECRET_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:3000&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_eval_fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&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;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safe_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&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;run_agent_with_tracing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Run the LangGraph agent with LangFuse tracing attached.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;your_agent&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;graph&lt;/span&gt; &lt;span class="c1"&gt;# your compiled LangGraph graph
&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;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&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;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;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="n"&gt;test_input&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_message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}]},&lt;/span&gt;
        &lt;span class="n"&gt;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;callbacks&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;LANGFUSE_HANDLER&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;evaluate_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agent_output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;test_case&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LLMTestCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;test_input&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_message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;actual_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;agent_output&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;final_response&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;retrieval_context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;agent_output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;retrieved_chunks&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;metrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;AnswerRelevancyMetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;FaithfulnessMetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nc"&gt;HallucinationMetric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;test_case&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The callback handler is the key integration point — LangFuse hooks into every LangGraph step automatically through the callbacks mechanism, so you get full trace visibility without changing your agent code.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The Reviewer Comments That Shaped the Design
&lt;/h3&gt;

&lt;p&gt;We opened this design up for internal review, expecting mostly rubber-stamping. We got something more useful: a few pointed questions that fundamentally shaped how we thought about the problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Which of these metrics are deterministic and which use an LLM judge?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This turned out to be more important than it first appeared.&lt;/em&gt; Deterministic checks — did tool X get called, did parameter Y have value Z — are stable across runs. LLM-judge metrics are not. They can vary based on which model you use, how the prompt template is phrased, and even non-determinism in the judge model itself. A score of 0.83 today might be 0.79 tomorrow not because your agent got worse, but because the judge got slightly different. We needed to track these separately and be explicit about which was which in our YAML fixtures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“What’s the judge model, and do you have a plan for judge model bias?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you’re using GPT-4 as your judge and your agent is also using GPT-4, you’re likely getting inflated scores — the judge model tends to favor outputs that look like its own outputs.&lt;/em&gt; We added a note in our evaluation config to pin the judge model to a different provider than the agent model, and to document this explicitly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Where do I see the scores? Per-run? Aggregated over time?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The answer at that point was “in the terminal.” That wasn’t good enough. We added LangFuse dashboards for score aggregation and set up alerts for when any metric dropped below threshold on consecutive runs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“What does a health check look like for the eval pipeline itself?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Good question. We added a canary fixture — a trivially easy test case that should always pass — that runs first in every eval job. &lt;em&gt;If the canary fails, something is wrong with the eval infrastructure, not the agent, and the run is aborted before generating misleading results.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Small comments. They had a bigger impact on the final design than most of the actual architecture decisions.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. The AWS Alternative: AgentCore Evaluations
&lt;/h3&gt;

&lt;p&gt;While we were finalizing the LangFuse + Ragas + DeepEval design, our colleague Maciej was separately evaluating AWS’s native evaluation offering through Bedrock AgentCore. The pitch was compelling: fewer moving parts, native tracing that integrates with everything else in the AgentCore stack, and a production path that doesn’t require managing three separate services.&lt;/p&gt;

&lt;p&gt;The proposal was to run a hybrid approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+----------------------+-----------------------------+
| Layer | Tool |
+----------------------+-----------------------------+
| Tracing | AgentCore native tracing |
| Built-in metrics | AgentCore Evaluations |
| Custom metrics | DeepEval (kept) |
| Test fixtures | .eval.yaml (kept) |
+----------------------+-----------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reduction in moving parts is real. Instead of running LangFuse locally or self-hosted, you lean on AgentCore’s built-in tracing. Instead of standing up Ragas metrics computation, you use AgentCore’s built-in faithfulness and context relevance metrics.&lt;/p&gt;

&lt;p&gt;We were genuinely tempted. The operational simplicity argument is hard to ignore when you’re a small team.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Where the Native Replacements Break Down
&lt;/h3&gt;

&lt;p&gt;Then we actually compared the metrics side by side. And this is where the “native” story got complicated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Faithfulness: Same Name, Different Problem
&lt;/h3&gt;

&lt;p&gt;AgentCore provides a metric called Builtin.Faithfulness. Ragas provides a metric called faithfulness. They sound equivalent. They are not.&lt;/p&gt;

&lt;p&gt;Ragas faithfulness asks: &lt;em&gt;“Are the claims in the response supported by the retrieved context?”&lt;/em&gt; It decomposes the response into individual claims, checks each claim against the context, and computes a ratio. It’s specifically a RAG faithfulness check.&lt;/p&gt;

&lt;p&gt;AgentCore’s Builtin.Faithfulness asks: &lt;em&gt;"Is the response consistent with the input prompt and conversation history?"&lt;/em&gt; That's a consistency check, not a grounding check. For a RAG-heavy agent, these catch completely different failure modes. You can pass one and fail the other.&lt;/p&gt;

&lt;p&gt;If you swap Ragas faithfulness for AgentCore’s built-in and call it a day, you’ve silently dropped one of your most important safety checks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Context Relevance vs. Context Precision
&lt;/h3&gt;

&lt;p&gt;Similar story with retrieval quality. Ragas has context_precision, which measures whether the retrieved chunks that were actually &lt;em&gt;used&lt;/em&gt; in the response were among the most relevant ones available. It's a quality-of-retrieval metric — it penalizes you for retrieving ten chunks but only using the bottom three.&lt;/p&gt;

&lt;p&gt;AgentCore’s ContextRelevance measures whether the retrieved context is relevant to the input query at all. That's a different, weaker check. Passing context relevance just means you retrieved &lt;em&gt;something&lt;/em&gt; related to the question. It says nothing about whether the retrieval was precise or whether the agent used the best available context.&lt;/p&gt;

&lt;h4&gt;
  
  
  Here’s a Side-by-Side Summary
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+---------------------+----------------------------------+----------------------------------+
| Metric | Ragas | AgentCore |
+---------------------+----------------------------------+----------------------------------+
| faithfulness | Claims grounded in retrieved | Response consistent with |
| | context (RAG grounding check) | conversation history |
+---------------------+----------------------------------+----------------------------------+
| context quality | context_precision: were the | ContextRelevance: is retrieved |
| | best chunks selected? | context related to the query? |
+---------------------+----------------------------------+----------------------------------+
| answer relevance | answer_relevancy: does response | Similar — reasonable overlap |
| | address the question? | here |
+---------------------+----------------------------------+----------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The name similarity is what makes this dangerous. You could swap these metrics, see similar-looking scores on a simple test case, and conclude the migration is safe. The divergence only shows up on the cases where it matters — complex multi-hop retrieval, sparse context, adversarial inputs.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. The Decision Framework: PoC First
&lt;/h3&gt;

&lt;p&gt;Evaluation infrastructure is not business logic. You can swap it out. But you can also create invisible regressions if you swap it out carelessly — which is exactly what the faithfulness naming collision would have caused.&lt;/p&gt;

&lt;p&gt;Instead, we defined a three-outcome PoC:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-------------------+------------------------------------------+
| Outcome | Action |
+-------------------+------------------------------------------+
| Adopt | AgentCore metrics are equivalent or |
| | better — migrate fully |
+-------------------+------------------------------------------+
| Swap | Some AgentCore metrics work, others |
| | don't — hybrid approach |
+-------------------+------------------------------------------+
| Build custom | Neither works well enough — write |
| | custom metric using AgentCore's |
| | custom evaluator API |
+-------------------+------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The PoC scope was intentionally small: run the same 10 eval fixtures through both stacks in parallel, compare scores for the same inputs, and flag any case where the scores diverge by more than 15%. Two weeks of data. One shared dashboard.&lt;/p&gt;

&lt;p&gt;That’s a much cheaper way to answer the question than migrating your entire eval pipeline and discovering the issue three months into production.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. A Minimal Local Eval Setup with Ollama
&lt;/h3&gt;

&lt;p&gt;If you want to experiment with this pattern without spinning up cloud infrastructure, here’s a fully local setup using Ollama as the LLM judge. This runs entirely on your machine.&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="c1"&gt;# docker-compose.eval-local.yml&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.9"&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;ollama&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ollama/ollama:latest&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;11434:11434"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ollama_data:/root/.ollama&lt;/span&gt;
    &lt;span class="c1"&gt;# Pull a model after startup: docker exec -it ollama ollama pull llama3&lt;/span&gt;

  &lt;span class="na"&gt;langfuse&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;langfuse/langfuse:latest&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL=postgresql://langfuse:langfuse@postgres:5432/langfuse&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NEXTAUTH_SECRET=dev-secret-change-in-prod&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NEXTAUTH_URL=http://localhost:3000&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SALT=dev-salt&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;

  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:15&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;langfuse&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;langfuse&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;langfuse&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pgdata:/var/lib/postgresql/data&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ollama_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pgdata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="c1"&gt;# eval_local.py — uses Ollama as the judge model&lt;/span&gt;
&lt;span class="s"&gt;import os&lt;/span&gt;
&lt;span class="s"&gt;from deepeval.models.base_model import DeepEvalBaseLLM&lt;/span&gt;
&lt;span class="s"&gt;from deepeval.metrics import AnswerRelevancyMetric, FaithfulnessMetric&lt;/span&gt;
&lt;span class="s"&gt;from deepeval.test_case import LLMTestCase&lt;/span&gt;
&lt;span class="s"&gt;from deepeval import evaluate&lt;/span&gt;
&lt;span class="s"&gt;import requests&lt;/span&gt;
&lt;span class="s"&gt;import json&lt;/span&gt;

&lt;span class="na"&gt;class OllamaJudge(DeepEvalBaseLLM)&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Custom&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;DeepEval&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;judge&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;backed&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;by&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;local&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Ollama&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;model."&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;

    &lt;span class="s"&gt;def __init__ (self, model_name&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;str = "llama3")&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
        &lt;span class="s"&gt;self.model_name = model_name&lt;/span&gt;

    &lt;span class="s"&gt;def load_model(self)&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
        &lt;span class="s"&gt;return self&lt;/span&gt;

    &lt;span class="s"&gt;def generate(self, prompt&lt;/span&gt;&lt;span class="na"&gt;: str) -&amp;gt; str&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s"&gt;response = requests.post(&lt;/span&gt;
            &lt;span class="s"&gt;"http://localhost:11434/api/generate",&lt;/span&gt;
            &lt;span class="s"&gt;json={"model"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;self.model_name, "prompt"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prompt, "stream"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;False},&lt;/span&gt;
        &lt;span class="s"&gt;)&lt;/span&gt;
        &lt;span class="s"&gt;return response.json()["response"]&lt;/span&gt;

    &lt;span class="s"&gt;async def a_generate(self, prompt&lt;/span&gt;&lt;span class="na"&gt;: str) -&amp;gt; str&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s"&gt;return self.generate(prompt)&lt;/span&gt;

    &lt;span class="na"&gt;def get_model_name(self) -&amp;gt; str&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="s"&gt;return f"ollama/{self.model_name}"&lt;/span&gt;

&lt;span class="na"&gt;def run_local_eval(test_cases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;list[dict])&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;judge = OllamaJudge(model_name="llama3")&lt;/span&gt;

    &lt;span class="s"&gt;cases = [&lt;/span&gt;
        &lt;span class="s"&gt;LLMTestCase(&lt;/span&gt;
            &lt;span class="s"&gt;input=tc["input"],&lt;/span&gt;
            &lt;span class="s"&gt;actual_output=tc["output"],&lt;/span&gt;
            &lt;span class="s"&gt;retrieval_context=tc.get("context", []),&lt;/span&gt;
        &lt;span class="s"&gt;)&lt;/span&gt;
        &lt;span class="s"&gt;for tc in test_cases&lt;/span&gt;
    &lt;span class="s"&gt;]&lt;/span&gt;

    &lt;span class="s"&gt;metrics = [&lt;/span&gt;
        &lt;span class="s"&gt;AnswerRelevancyMetric(threshold=0.7, model=judge),&lt;/span&gt;
        &lt;span class="s"&gt;FaithfulnessMetric(threshold=0.7, model=judge),&lt;/span&gt;
    &lt;span class="s"&gt;]&lt;/span&gt;

    &lt;span class="s"&gt;evaluate(cases, metrics)&lt;/span&gt;

&lt;span class="s"&gt;if __name__ == " __main__"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;sample_cases = [&lt;/span&gt;
        &lt;span class="s"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"input"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;time&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;check-out?"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Check-out&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;at&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;11:00&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;AM.&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Late&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;check-out&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;until&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;PM&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;available&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;an&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;additional&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;fee."&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;context"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;
                &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hotel&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;policy:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;standard&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;check-out&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;at&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;11:00&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;AM."&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
                &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Late&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;check-out&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;available&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;until&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;14:00&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;EUR&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;30."&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt;
            &lt;span class="pi"&gt;]&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;
        &lt;span class="err"&gt;}&lt;/span&gt;
    &lt;span class="err"&gt;]&lt;/span&gt;
    &lt;span class="s"&gt;run_local_eval(sample_cases)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start everything with docker compose -f docker-compose.eval-local.yml up -d, pull a model with docker exec -it  ollama pull llama3, and you have a fully local eval stack with no API keys, no cloud costs, and no data leaving your machine.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Lessons Learned
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Eval infrastructure is not your core product — treat it as something you can swap.&lt;/strong&gt; Don’t get attached to a specific tool. What matters is the fixture format and the success criteria. If your .eval.yaml files are tool-agnostic, you can migrate the underlying runner without losing any of the work you put into defining good tests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Native” does not mean “equivalent.”&lt;/strong&gt; This seems obvious in retrospect, but the naming similarity between AgentCore’s Builtin.Faithfulness and Ragas's faithfulness is genuinely confusing. Always read the metric definition, not just the name. Check what it's actually measuring and whether that maps to the failure mode you care about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Name similarity is a trap, especially when you’re under time pressure.&lt;/strong&gt; When you’re evaluating tools quickly, you tend to match on names. That’s fine as a first pass, but it needs to be followed by an actual comparison on real data before you commit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep your PoC scope small.&lt;/strong&gt; Two weeks, ten fixtures, one shared dashboard. That’s enough to make a data-driven decision. The instinct to do a “comprehensive evaluation” before deciding is usually a way to delay the decision indefinitely. Define the minimum evidence you’d need to choose, run the experiment to get it, then choose.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deterministic and LLM-judge metrics are different animals.&lt;/strong&gt; Keep them separate in your fixtures, track them separately in your dashboards, and don’t conflate a drop in one with a drop in the other. A regression in tool-call correctness (deterministic) is a different kind of problem than a regression in faithfulness score (LLM judge) and needs a different debugging approach.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The eval stack is never really done. New failure modes emerge, judge models get updated, new metrics become available. But if you invest upfront in a good fixture format and a clear framework for comparing evaluation tools, you’re set up to evolve the infrastructure without losing ground. And the next time someone asks “but how do you know it works?” — you have an answer.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;If you’ve run into interesting eval challenges with LangGraph or other agent frameworks, I’d be curious what metrics ended up being most useful for you. The more people share on this, the better the whole ecosystem gets.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>langgraph</category>
      <category>aiagentdevelopment</category>
      <category>amazonbedrock</category>
      <category>llmops</category>
    </item>
    <item>
      <title>Agentic Architectures — Article 4: Agentic Protocols (MCP and A2A)</title>
      <dc:creator>Ali Suleyman TOPUZ</dc:creator>
      <pubDate>Tue, 31 Mar 2026 16:42:13 +0000</pubDate>
      <link>https://forem.com/topuzas/agentic-architectures-article-4-agentic-protocols-mcp-and-a2a-1577</link>
      <guid>https://forem.com/topuzas/agentic-architectures-article-4-agentic-protocols-mcp-and-a2a-1577</guid>
      <description>&lt;h3&gt;
  
  
  Interoperability and the “Connective Tissue” of AI
&lt;/h3&gt;

&lt;p&gt;Every mature technology ecosystem eventually hits the same wall. Early on, everyone builds their own integrations — custom API wrappers, bespoke data formats, proprietary communication layers. It works, until the ecosystem grows large enough that the integration cost becomes the dominant cost. Then someone proposes a standard, half the industry argues about it for two years, and eventually something wins.&lt;/p&gt;

&lt;p&gt;The agentic AI ecosystem is hitting that wall right now.&lt;/p&gt;

&lt;p&gt;A year ago, if you wanted your agent to read files from your local filesystem, query your database, and post a summary to Slack, you wrote three custom integrations. If you wanted two agents from different vendors to hand off a task, you wrote a custom serialization format and hoped both sides agreed on what “done” meant. Every team was solving the same plumbing problems independently, and none of the pipes connected.&lt;/p&gt;

&lt;p&gt;Two protocols are emerging to fix this. The &lt;strong&gt;Model Context Protocol (MCP)&lt;/strong&gt; standardizes how agents connect to tools and data sources. &lt;strong&gt;Agent-to-Agent (A2A)&lt;/strong&gt; standardizes how agents talk to each other. Together, they are becoming the connective tissue of the agentic ecosystem — the infrastructure layer that lets you stop thinking about plumbing and start thinking about what your agents actually do.&lt;/p&gt;

&lt;p&gt;This article is about both: what they are, how they work, and what production deployment with them actually looks like.&lt;/p&gt;

&lt;h3&gt;
  
  
  Model Context Protocol: One Interface to Rule Them All
&lt;/h3&gt;

&lt;p&gt;Before MCP, every agent-to-tool integration was a bespoke engineering project. Want your agent to read from a PostgreSQL database? Write a tool wrapper. Want it to search Confluence? Write another wrapper. Want it to list files in an S3 bucket? Another wrapper. Each wrapper has its own error handling, its own authentication scheme, its own data format. You end up with a collection of brittle, hard-to-maintain glue code that grows proportionally with every new tool you add.&lt;/p&gt;

&lt;p&gt;Anthropic introduced MCP in late 2024, and the core insight is simple: if every tool exposes the same interface, agents only need to learn one way to talk to tools.&lt;/p&gt;

&lt;p&gt;MCP defines a &lt;strong&gt;standardized JSON-RPC interface&lt;/strong&gt; between a “host” (the agent or the application running it) and a “server” (any tool or data source). The protocol specifies three primitive types that a server can expose:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Resources&lt;/strong&gt;  — data that the agent can read (files, database rows, API responses, calendar entries)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tools&lt;/strong&gt;  — functions the agent can invoke with parameters (send an email, create a Jira ticket, run a SQL query)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompts&lt;/strong&gt;  — reusable prompt templates that the server exposes for the agent to use in context&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The communication looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent (MCP Host) MCP Server (e.g., Filesystem)
      | |
      |── initialize() ────────────────────────&amp;gt;|
      |&amp;lt;─ capabilities (resources, tools) ──────|
      | |
      |── tools/list() ────────────────────────&amp;gt;|
      |&amp;lt;─ [read_file, write_file, list_dir] ────|
      | |
      |── tools/call(read_file, {path}) ────────&amp;gt;|
      |&amp;lt;─ {content: "..."} ─────────────────────|
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What makes this powerful is that the agent doesn’t need to know anything about the underlying data source. It just knows: here is a list of tools available on this server, here are their schemas, here is how to call them. The MCP server handles the actual implementation — the filesystem calls, the database queries, the API authentication.&lt;/p&gt;

&lt;p&gt;The practical consequence is that an agent built on MCP can connect to any MCP-compatible server without custom integration code. Your Slack workspace, your local filesystem, your PostgreSQL database, your Google Calendar — if there’s an MCP server for it (and increasingly, there is), your agent can use it out of the box.&lt;/p&gt;

&lt;h3&gt;
  
  
  How MCP Gives Agents Context They Couldn’t Have Before
&lt;/h3&gt;

&lt;p&gt;The “context” in Model Context Protocol is doing real work. One of the fundamental limitations of LLM-based agents has always been that their knowledge is frozen at training time — they know what they were trained on, and nothing that happened after the cutoff date. RAG helps with some of this, but it’s fundamentally a retrieval problem: you have to know what to retrieve.&lt;/p&gt;

&lt;p&gt;MCP takes a different approach. Instead of retrieving information and injecting it into the prompt, it gives the agent &lt;strong&gt;live access to the systems where your information actually lives&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Consider the difference in practice. A customer support agent without MCP retrieves customer history from a vector store populated by a nightly batch job. The information is at least a day old, possibly more, and it’s a lossy representation — embeddings capture semantic meaning but lose precise details. An MCP-enabled agent with access to your CRM’s MCP server reads the customer record directly, in real time, with full fidelity.&lt;/p&gt;

&lt;p&gt;The agent can now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;See the customer’s last three support tickets — not summaries, the actual tickets&lt;/li&gt;
&lt;li&gt;Check their current subscription status — not a cached version, the live record&lt;/li&gt;
&lt;li&gt;Read the internal notes the account manager left yesterday&lt;/li&gt;
&lt;li&gt;Look at the open invoices in your billing system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this required a custom integration. It required an MCP server for your CRM, an MCP server for your billing system, and an agent configured to connect to both.&lt;/p&gt;

&lt;p&gt;The architectural implication is significant: MCP shifts the integration burden from the agent developer to the tool developer. Once a tool has an MCP server, any MCP-compatible agent can use it. This is the same network effect that made REST APIs dominant — not because REST was technically superior in every dimension, but because a common standard made the ecosystem composable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Agent-to-Agent Communication: Defining a Common Language
&lt;/h3&gt;

&lt;p&gt;MCP solves the agent-to-tool problem. A2A solves the agent-to-agent problem, and it’s a harder one.&lt;/p&gt;

&lt;p&gt;When two agents need to collaborate on a task, they face a set of questions that are easy to answer between humans but surprisingly tricky to standardize for software:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How does Agent A tell Agent B what it needs?&lt;/li&gt;
&lt;li&gt;How does Agent B signal that it’s accepted the task, is working on it, or has completed it?&lt;/li&gt;
&lt;li&gt;What format does the result come back in?&lt;/li&gt;
&lt;li&gt;What happens if Agent B can only partially complete the request?&lt;/li&gt;
&lt;li&gt;How does Agent A know Agent B is trustworthy?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;strong&gt;A2A protocol&lt;/strong&gt; (developed collaboratively by Google and a consortium of enterprise technology vendors, with broad industry participation) defines a standard vocabulary for all of these interactions. Like MCP, it’s built on JSON-RPC, which means it’s transport-agnostic and integrates cleanly with existing HTTP infrastructure.&lt;/p&gt;

&lt;p&gt;The core concept in A2A is the &lt;strong&gt;Task&lt;/strong&gt;  — a unit of work that one agent requests from another. A Task has a lifecycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;submitted → working → [input-required] → working → completed
                                                  → failed
                                                  → cancelled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agent A submits a Task to Agent B’s endpoint. Agent B acknowledges with a task ID and status. Agent A can poll for updates or receive streaming events as Agent B makes progress. When Agent B completes the task, it returns a structured result. If something goes wrong, it returns a structured error with enough context for Agent A to decide what to do next.&lt;/p&gt;

&lt;p&gt;What makes this more than just a REST API convention is the &lt;strong&gt;Agent Card&lt;/strong&gt;  — a machine-readable document that each agent publishes at a well-known endpoint, describing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What tasks it can accept (its capabilities)&lt;/li&gt;
&lt;li&gt;What authentication it requires&lt;/li&gt;
&lt;li&gt;What input formats it accepts and what output formats it produces&lt;/li&gt;
&lt;li&gt;Its current availability and load&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An orchestrator agent discovering a new peer doesn’t need documentation or a human to explain the integration. It reads the Agent Card, understands the capabilities, and knows how to submit tasks. The protocol handles the rest.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Contract-Net Protocol: Agents That Bid on Work
&lt;/h3&gt;

&lt;p&gt;One of the more elegant ideas in the A2A ecosystem is borrowed from classical distributed AI: the &lt;strong&gt;Contract-Net Protocol&lt;/strong&gt; , originally proposed in the 1980s and now finding new relevance in the agentic era.&lt;/p&gt;

&lt;p&gt;The idea is that task assignment shouldn’t be static — orchestrators shouldn’t hardcode which agent handles which task type. Instead, agents should be able to &lt;strong&gt;bid&lt;/strong&gt; on tasks based on their current state, capabilities, and load.&lt;/p&gt;

&lt;p&gt;The flow works like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Orchestrator broadcasts task announcement
        ↓
Available agents evaluate: Can I do this? At what cost? How fast?
        ↓
Interested agents submit bids (capability match, estimated latency, current load)
        ↓
Orchestrator evaluates bids and awards task to winning agent
        ↓
Winning agent executes, reports completion
        ↓
Orchestrator releases other agents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In practice, a bid might contain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Capability score&lt;/strong&gt; : How well does this agent’s specialization match the task requirements? (0.0 to 1.0)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Estimated completion time&lt;/strong&gt; : Based on current queue depth and task complexity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource cost estimate&lt;/strong&gt; : How many tokens, compute cycles, or API calls will this take?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confidence level&lt;/strong&gt; : How certain is the agent that it can complete this task successfully?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The orchestrator applies a selection policy — lowest cost, fastest completion, highest confidence, or a weighted combination — and awards the contract.&lt;/p&gt;

&lt;p&gt;This pattern is particularly valuable in systems where agent load is uneven. A Coder Agent might be heavily loaded while a Reviewer Agent is idle. Without bidding, the orchestrator has no visibility into this. With bidding, the idle Reviewer Agent can bid aggressively on tasks that are near its competency boundary, while the overloaded Coder Agent bids conservatively or not at all.&lt;/p&gt;

&lt;p&gt;The Contract-Net Protocol also provides natural load balancing for horizontally scaled agent pools. If you’re running three instances of the same agent type, whichever instance is least loaded will submit the most competitive bid. The orchestrator doesn’t need to know anything about instance count or load distribution — the bidding mechanism handles it automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security &amp;amp; Identity: How an Agent Proves Who It Is
&lt;/h3&gt;

&lt;p&gt;This is the section that gets skipped in tutorials and becomes an urgent problem in production. When Agent A calls Agent B’s endpoint, Agent B needs to answer a question that is non-trivial: &lt;em&gt;is this request actually coming from a trusted agent in my system, or is someone impersonating it?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In human-facing systems, we solve this with OAuth 2.0 and OIDC — the user authenticates with an identity provider, gets a token, and presents that token to services. The same pattern applies to agents, with some important adaptations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OIDC for Agents&lt;/strong&gt; (increasingly referred to as Workload Identity in the cloud provider ecosystem) works like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent Runtime Identity Provider Downstream Service
      | | |
      |── request token ───────────&amp;gt;| |
      |&amp;lt;─ signed JWT (agent ID) ────| |
      | | |
      |── call with JWT ────────────────────────────────────&amp;gt; |
      | | verify signature ────&amp;gt; |
      | |&amp;lt;─ valid, proceed ─────── |
      |&amp;lt;─ response ─────────────────────────────────────────── |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key components:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent Identity Token&lt;/strong&gt;  — A short-lived JWT issued by your identity provider that asserts the agent’s identity, role, and the specific permissions it has been granted. “I am the CRM-Reader agent, issued by your organization’s IDP, and I am authorized to read customer records but not write them.” The token is signed by the IDP; the downstream service verifies the signature without needing to call the IDP on every request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scoped Permissions&lt;/strong&gt;  — Each agent should have a token scoped to the minimum permissions it needs for its function. The Coder Agent doesn’t need write access to the CRM. The Customer Service Agent doesn’t need access to the code repository. Principle of least privilege applies to agents exactly as it does to human users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Token Rotation&lt;/strong&gt;  — Agent tokens should be short-lived (15–60 minutes) and automatically rotated. This limits the blast radius if a token is compromised. The agent runtime handles rotation transparently — the agent doesn’t need to manage its own credential lifecycle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audit Logging&lt;/strong&gt;  — Every action an agent takes should be logged with its identity token. When you need to answer “which agent accessed this customer record at 14:32 yesterday and why,” the audit log should give you a precise answer. This is not optional in regulated industries; it’s increasingly expected everywhere.&lt;/p&gt;

&lt;p&gt;On AWS, this pattern maps naturally to IAM Roles for Tasks (ECS) or Pod Identity (EKS). On the Bedrock AgentCore Runtime, each agent execution context gets an IAM role with the permissions defined at deployment time. The agent never handles long-lived credentials — the runtime injects temporary credentials into the execution environment automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Discovery Services: Building the Agent Registry
&lt;/h3&gt;

&lt;p&gt;As your agent ecosystem grows, a new operational problem emerges: how does an orchestrator find the right agent for a given task? Hardcoding agent endpoints into orchestrator logic works for two or three agents. It becomes a maintenance liability at ten, and an operational nightmare at fifty.&lt;/p&gt;

&lt;p&gt;The solution is borrowed directly from service mesh architecture: a &lt;strong&gt;Discovery Service&lt;/strong&gt;  — a registry where agents advertise their presence, capabilities, and health, and where orchestrators query to find appropriate peers.&lt;/p&gt;

&lt;p&gt;The concept maps to familiar infrastructure patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Eureka&lt;/strong&gt; (Netflix’s service registry) and &lt;strong&gt;Consul&lt;/strong&gt; (HashiCorp’s service mesh) solve this problem for microservices. The same principles apply to agent registries.&lt;/li&gt;
&lt;li&gt;In the Kubernetes ecosystem, this maps naturally to Service resources and endpoint discovery.&lt;/li&gt;
&lt;li&gt;In the cloud-native agentic ecosystem, the A2A Agent Card serves as the registration payload.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A well-designed Agent Registry exposes two primary interfaces:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Registration&lt;/strong&gt;  — Agents announce themselves on startup and deregister on shutdown. The registration payload includes the Agent Card (capabilities, input/output schemas, authentication requirements) plus runtime metadata (current load, health status, version).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Discovery&lt;/strong&gt;  — Orchestrators query the registry with a capability description: “I need an agent that can process PDF documents, write to a SQL database, and respond within 5 seconds.” The registry returns a ranked list of matching agents, filtered by health status and sorted by relevance score.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent Startup Registry Orchestrator
      |── register(AgentCard) ──&amp;gt;| |
      |&amp;lt;─ registered (id) ───────| |
      | |&amp;lt;─ discover(capability query) ───|
      | |── [Agent A, Agent B] ──────────&amp;gt;|
      |&amp;lt;─ task submission ────────────────────────────────────────|
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Health checking is essential. An agent that has registered but stopped responding is worse than an absent agent — it will be selected for tasks it can’t complete, causing failures and retries. The registry should actively probe registered agents on a regular heartbeat interval and automatically deregister agents that miss consecutive health checks.&lt;/p&gt;

&lt;p&gt;The discovery query language deserves careful design. Simple string matching on capability names breaks down quickly — “summarization” and “document summarization” and “text condensation” might all refer to the same capability. A well-designed registry uses structured capability taxonomies (standardized tags from a shared vocabulary) rather than free-text descriptions, ensuring that capability matching is reliable rather than approximate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Putting It Together: The Full Protocol Stack
&lt;/h3&gt;

&lt;p&gt;Across this series, we’ve built up a complete picture of what a production agentic system looks like. The protocol layer is where all of it connects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────────┐
│ USER / APPLICATION │
└───────────────────────────────┬─────────────────────────────────┘
                                │
┌───────────────────────────────▼─────────────────────────────────┐
│ ORCHESTRATOR AGENT │
│ (Hierarchical Planning, ReAct Loop) │
│ [Article 2 patterns] │
└──────────┬──────────────────────────────────┬───────────────────┘
           │ │
    A2A Protocol A2A Protocol
           │ │
┌──────────▼──────────┐ ┌──────────▼──────────┐
│ SPECIALIST AGENT │ │ SPECIALIST AGENT │
│ (Coder / Writer) │ │ (Reviewer / Critic)│
│ [Article 2 &amp;amp; 3] │ │ [Article 2 &amp;amp; 3] │
└──────────┬──────────┘ └──────────┬──────────┘
           │ │
    MCP Protocol MCP Protocol
           │ │
┌──────────▼──────────┐ ┌──────────▼──────────┐
│ MCP SERVER │ │ MCP SERVER │
│ (Filesystem / DB) │ │ (Slack / Calendar) │
└─────────────────────┘ └─────────────────────┘
           │ │
     [AgentOps Layer: OTel, Guardrails, HITL — Article 3]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MCP handles the vertical connections — agents to tools and data. A2A handles the horizontal connections — agents to agents. The AgentOps layer (observability, guardrails, eval pipelines, HITL) sits across all of it, providing the operational visibility and control that makes the whole system trustworthy in production.&lt;/p&gt;

&lt;p&gt;The Maturity Model from Article 1 maps onto this stack naturally: L1 and L2 systems use neither MCP nor A2A. L3 systems benefit significantly from MCP — standardizing tool access reduces integration overhead and makes the agent more capable without custom code. L4 and L5 systems need both — A2A for agent-to-agent coordination and MCP for tool access, with the AgentOps layer making the whole thing operable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Production Reality Check
&lt;/h3&gt;

&lt;p&gt;MCP and A2A are genuine improvements over the integration chaos they replace. They’re also early-stage standards in an ecosystem that is moving fast, and production adoption comes with real caveats.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP server quality is uneven.&lt;/strong&gt; The protocol is well-designed, but the ecosystem of available servers ranges from production-ready to experimental. Before adopting a community-maintained MCP server for a critical tool, audit its error handling, its authentication implementation, and how actively it’s maintained. A poorly implemented MCP server that swallows errors silently is harder to debug than a custom integration that fails loudly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A2A task lifecycle management requires discipline.&lt;/strong&gt; The protocol defines task states clearly, but implementing correct lifecycle management — handling timeouts, zombie tasks that never complete, cascade failures when a Worker agent goes down mid-task — requires careful engineering. Don’t assume the protocol handles operational edge cases for you; it defines the interface, not the reliability.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Discovery services add operational surface area.&lt;/strong&gt; A registry is another system to operate, monitor, and keep highly available. If your registry goes down, your orchestrators can’t find agents. Design for registry failure explicitly: agents should cache recent discovery results, orchestrators should have fallback direct-connection configurations for critical agents, and your monitoring should alert on registry health before it affects agent routing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Identity and security are non-negotiable at scale.&lt;/strong&gt; It’s tempting to skip the OIDC integration during early development and use shared API keys for agent-to-agent authentication. This is fine for a proof of concept and a liability in production. Build the identity layer before you scale, not after — retrofitting workload identity into a running multi-agent system is significantly more painful than designing it in from the start.&lt;/p&gt;

&lt;p&gt;The practical adoption path that has worked well: start with MCP for tool integrations (the ROI is immediate and the risk is low), add A2A when you have multiple agents that need to coordinate (and not before), build the identity layer in parallel with A2A adoption, and add a discovery service when you have more than five distinct agent types in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Closing the Series
&lt;/h3&gt;

&lt;p&gt;Over four articles, we’ve covered the full arc of agentic system design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Article 1&lt;/strong&gt; gave us the vocabulary — five levels of maturity, mapped to the infrastructure and cost reality of each.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Article 2&lt;/strong&gt; gave us the reasoning patterns — how agents plan, reflect, coordinate, and share knowledge without drowning in state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Article 3&lt;/strong&gt; gave us the operational discipline — observability, safety, evaluation, and the human checkpoints that keep the system trustworthy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Article 4&lt;/strong&gt; gave us the protocols — the standardized interfaces that make agents composable, discoverable, and secure at scale.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The through-line across all four is a consistent argument: the intelligence of your agent system is not primarily determined by the model you choose. It’s determined by the architecture around the model — the planning patterns, the memory design, the error handling, the observability, the coordination protocols. Models are commoditizing. Architecture is the durable differentiator.&lt;/p&gt;

&lt;p&gt;The teams building production agentic systems that actually work — not just in demos, but at scale, with real users, over time — are the ones treating AI like the distributed systems discipline it has become. The tools, protocols, and patterns in this series are what that looks like in practice.&lt;/p&gt;

&lt;p&gt;Build carefully. Measure everything. Ship incrementally. And set a maximum iteration count on your reflection loops.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  L1: Stateless L2: Tool-Augmented L3: Autonomous L4: Multi-Agent L5: Self-Correcting
------------------------------------------------------------------------------------------------------------------------------------------------------
Execution Serverless / Edge Serverless + integr. Long-running container Distributed orchestrator Distributed + feedback loops
State None None Short + long-term memory Shared state across agents State + mutation history
Latency profile Predictable Slightly variable Variable (loop-dependent) High, parallelizable Highest, bounded by budget
Cost model Linear (tokens) Linear + tool costs Nonlinear (calls per task) Nonlinear × agent count Nonlinear × iteration count
Primary failure Bad retrieval Tool hallucination Context overflow Cascade failures Runaway loops
Observability Basic logging Tool call tracing Full trace per loop Cross-agent tracing Cost + quality dashboards
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Included for reference from &lt;a href="https://medium.com/@topuzas/agentic-architectures-article-1-the-agentic-ai-maturity-model-092f009cf2c0" rel="noopener noreferrer"&gt;Article 1&lt;/a&gt; — MCP maps primarily to L2 and L3. A2A maps primarily to L4 and L5.&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>architecture</category>
      <category>api</category>
      <category>artificialintelligen</category>
    </item>
    <item>
      <title>Agentic Architectures — Article 3: AgentOps</title>
      <dc:creator>Ali Suleyman TOPUZ</dc:creator>
      <pubDate>Tue, 31 Mar 2026 16:42:00 +0000</pubDate>
      <link>https://forem.com/topuzas/agentic-architectures-article-3-agentops-3ba2</link>
      <guid>https://forem.com/topuzas/agentic-architectures-article-3-agentops-3ba2</guid>
      <description>&lt;h3&gt;
  
  
  Treating AI Like the Distributed System It Actually Is
&lt;/h3&gt;

&lt;p&gt;There’s a moment every team hits, usually somewhere between the third demo and the first real production deployment. The agent works beautifully in the notebook. It handles every test case you throw at it. You ship it. And then, three days later, you get a Slack message from a user that says something like: &lt;em&gt;“It’s been running for 20 minutes and nothing is happening.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You open the logs. There are no logs. The agent made 47 API calls, hit a rate limit on call 12, entered an undocumented retry state, and has been quietly spinning ever since — accumulating token costs, holding open a connection, and doing absolutely nothing useful.&lt;/p&gt;

&lt;p&gt;Welcome to production.&lt;/p&gt;

&lt;p&gt;The discipline of &lt;strong&gt;AgentOps&lt;/strong&gt; exists because agentic systems are distributed systems, and distributed systems fail in distributed ways — partially, silently, and at the worst possible time. The practices in this article aren’t optional polish you add after launch. They’re the foundation that determines whether your system is operable when things go wrong. And they will go wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  Observability &amp;amp; Tracing: You Can’t Fix What You Can’t See
&lt;/h3&gt;

&lt;p&gt;In a traditional web application, a request comes in and a response goes out. If something breaks, you have a single trace to inspect — one thread of execution, one error to find.&lt;/p&gt;

&lt;p&gt;An agentic system doesn’t work like this. A single user request might spawn a Manager agent, three Worker agents, a Critic, and a tool execution layer. Each of these makes independent model calls. Some run in parallel. Each can fail independently. The user sees one thing — “the agent is thinking” — and behind that is a branching tree of execution that can fail at any node.&lt;/p&gt;

&lt;p&gt;Debugging this without proper tracing is like trying to debug a microservices outage by reading individual server logs with no correlation IDs. Technically possible. Practically miserable.&lt;/p&gt;

&lt;p&gt;The modern answer is &lt;strong&gt;OpenTelemetry&lt;/strong&gt; (OTel) — the vendor-neutral observability standard that has become the lingua franca of distributed systems monitoring. The good news is that both LangSmith (from the LangChain ecosystem) and Arize Phoenix support OTel-compatible trace ingestion, which means you can instrument your agent once and route traces to whichever backend you prefer.&lt;/p&gt;

&lt;p&gt;What you want to capture at every node in your agent graph:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Span start and end timestamps&lt;/strong&gt;  — so you can see exactly where time is being spent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model call metadata&lt;/strong&gt;  — which model, which prompt template version, input/output token counts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tool call inputs and outputs&lt;/strong&gt;  — what the agent asked the tool to do, and what it got back&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State transitions&lt;/strong&gt;  — when the agent moved from Planning to Executing to Reflecting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error events&lt;/strong&gt;  — with full context, not just the exception message&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The two metrics that matter most in production, and that most teams don’t track until they should:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trace Latency&lt;/strong&gt; is the wall-clock time from user request to final response, across the entire agent execution graph. Not just model latency — total latency, including tool calls, state reads and writes, and any waiting time. This is what the user experiences, and it’s almost always higher than you think because it includes all the overhead your benchmark tests don’t capture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Token Cost per Trace&lt;/strong&gt; is the total model spend for a single user task, aggregated across all agents and all model calls in the trace. In a multi-agent system, this is the number that will surprise you. Individual model calls look cheap. When you multiply them by agent count, loop iterations, and daily request volume, the number that emerges is frequently 5–10x what the team estimated during design.&lt;/p&gt;

&lt;p&gt;Build a dashboard with both of these as primary metrics before you launch, not after. The alert threshold for Trace Latency should trigger before your user-facing timeout does. The alert threshold for Token Cost per Trace should trigger before your monthly budget does.&lt;/p&gt;

&lt;h3&gt;
  
  
  Guardrails &amp;amp; Safety: The Gates That Protect the System
&lt;/h3&gt;

&lt;p&gt;Every agent that interacts with real users is a potential attack surface — not just for adversarial users, but for the model’s own failure modes. A guardrail is an enforcement layer that sits between the world and your agent, checking inputs before they reach the model and outputs before they reach the user.&lt;/p&gt;

&lt;p&gt;Think of it as two gates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Input → [INPUT GATE] → Agent → Model → [OUTPUT GATE] → User Response
                  ↓ ↓
             Block / Sanitize Block / Rewrite / Flag
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Input Gate&lt;/strong&gt; protects the model from harmful, manipulative, or out-of-scope inputs. Common implementations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LlamaGuard&lt;/strong&gt;  — Meta’s open-source safety classifier, trained specifically to detect harmful content categories (violence, hate speech, self-harm, illegal activity). It runs as a separate model call before your main agent, adding ~100–200ms of latency and a fraction of a cent in cost per request. Worth it for any user-facing deployment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regex and rule-based filters&lt;/strong&gt;  — Fast, cheap, and reliable for known patterns. Prompt injection attempts often have detectable signatures (ignore previous instructions, you are now, your new system prompt is). A well-maintained regex filter catches a meaningful percentage of these before they ever reach the model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM-based classifiers&lt;/strong&gt;  — For nuanced cases where rule-based filtering isn’t sufficient. A small, fast model (Haiku, GPT-4o mini) classifying the intent of an input before it hits your expensive main model is a good investment for high-value workflows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Output Gate&lt;/strong&gt; protects users from the model’s failure modes — hallucinations, off-topic responses, sensitive data leakage, and policy violations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PII detection&lt;/strong&gt;  — Before any agent output reaches a user or gets written to a log, scan it for personally identifiable information that shouldn’t be there. Regex handles the obvious cases (email patterns, credit card formats, SSN patterns). For subtler cases, a dedicated NER model does the job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Factual grounding checks&lt;/strong&gt;  — For RAG-based agents, verify that claims in the output can be traced back to retrieved source documents. Outputs that make claims not present in the source context should be flagged or blocked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output format validation&lt;/strong&gt;  — If your agent is supposed to return structured JSON, validate the schema before passing it downstream. A malformed output that crashes a downstream service is a guardrail failure, not a model failure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One implementation principle that’s easy to overlook: &lt;strong&gt;guardrails must fail safely.&lt;/strong&gt; If your Input Gate goes down, what happens? If the answer is “all user requests go through unfiltered,” you have a single point of failure in your safety layer. Design guardrails with explicit fallback behavior — if the safety classifier is unavailable, either queue the request or return a graceful error, never silently bypass the check.&lt;/p&gt;

&lt;h3&gt;
  
  
  Evaluation Pipelines: The Regression Test for Your Agent
&lt;/h3&gt;

&lt;p&gt;Software engineers have a deeply ingrained habit of writing tests before shipping code. Most AI teams don’t extend this habit to their agents — and they pay for it every time a prompt change breaks something in production that worked perfectly last week.&lt;/p&gt;

&lt;p&gt;The equivalent of a test suite for an agentic system is an &lt;strong&gt;Eval Pipeline&lt;/strong&gt; , and the core artifact it runs against is a &lt;strong&gt;Golden Dataset&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A Golden Dataset is a curated collection of input-output pairs that represent the behavior your system should exhibit. Each entry contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A realistic input (user query, document, task description)&lt;/li&gt;
&lt;li&gt;The expected output — or the criteria by which a good output can be evaluated&lt;/li&gt;
&lt;li&gt;Metadata: the scenario type, difficulty level, which agent capabilities it exercises&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Building a good Golden Dataset is not a one-time task. It grows over time, fed primarily by production failures. Every time your agent produces a wrong or unexpected output in production, that input — along with the correct output — gets added to the dataset. The Golden Dataset becomes a living record of every failure mode your system has ever exhibited and been fixed for.&lt;/p&gt;

&lt;p&gt;The Eval Pipeline runs this dataset against your agent automatically on every significant change — prompt updates, model version changes, tool modifications, new agent roles. The output is a regression report:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Golden Dataset Run — 2025-03-28
------------------------------------------------------------
Total cases: 847
Passed: 821 (96.9%)
Regressed: 18 (2.1%) ← These need investigation
Improved: 8 (0.9%)
------------------------------------------------------------
Regressed cases by category:
  Multi-step reasoning: 9
  Tool selection: 5
  Output format: 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The 18 regressions are the cases where a change that was supposed to improve things has broken something that worked before. Without the eval pipeline, you’d find these in production. With it, you find them in CI.&lt;/p&gt;

&lt;p&gt;Evaluation metrics vary by task type. For classification tasks, precision and recall. For generation tasks, you need LLM-based evaluation — a judge model that scores outputs against a rubric (this is increasingly standard and works well when the rubric is specific). For tool-use tasks, check whether the correct tools were called with correct arguments, independent of the final text output.&lt;/p&gt;

&lt;p&gt;One practical note: keep your Golden Dataset honest. The temptation is to add only cases your system handles well, which turns the eval into a confidence-boosting exercise rather than a quality gate. Actively seek out edge cases, adversarial inputs, and the kinds of queries that make your system stumble.&lt;/p&gt;

&lt;h3&gt;
  
  
  Agentic Error Handling: Building for the Inevitable
&lt;/h3&gt;

&lt;p&gt;A production agent will encounter rate limits. APIs will return 500 errors. Model calls will time out. Tools will return malformed responses. The question is not whether these things will happen — it’s whether your system handles them gracefully or catastrophically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exponential Backoff with Jitter&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When a model API returns a 429 (rate limit exceeded), the naive response is to retry immediately. This is exactly wrong — immediate retries hammer the rate-limited endpoint and make the congestion worse. The correct pattern is exponential backoff with jitter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Attempt 1: fail → wait 1s + random(0-500ms)
Attempt 2: fail → wait 2s + random(0-500ms)
Attempt 3: fail → wait 4s + random(0-500ms)
Attempt 4: fail → wait 8s + random(0-500ms)
Attempt 5: fail → give up, return error to orchestrator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The jitter (random delay) is critical in multi-agent systems. Without it, multiple agents hitting the same rate limit will retry in synchrony, creating thundering herd waves that make the rate limit problem worse. With jitter, retries spread out naturally.&lt;/p&gt;

&lt;p&gt;Set a maximum retry count (5 is a reasonable default) and a maximum total wait time (30–60 seconds for interactive tasks). After that, fail explicitly — a clean error the orchestrator can handle is better than a silent spin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fallback Models&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not all model failures are rate limits. Sometimes a model is genuinely unavailable, or a specific request exceeds the model’s context limit, or a cost budget has been hit. For these cases, build a fallback model hierarchy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Primary: Claude Sonnet (full capability, higher cost)
Fallback: Claude Haiku (reduced capability, lower cost)
Emergency: Cached response or template-based response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fallback trigger conditions and the fallback target should be explicit configuration, not hardcoded logic. Different tasks warrant different fallback strategies — a customer-facing response probably shouldn’t fall back to a template, but an internal classification task might be fine running on a smaller model.&lt;/p&gt;

&lt;p&gt;Critically, &lt;strong&gt;log every fallback event&lt;/strong&gt;. A spike in fallback usage is an early warning signal — it means your primary model is struggling for some reason, and you want to know about it before it becomes a full outage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Human-in-the-Loop: Designing Deliberate Pause Points
&lt;/h3&gt;

&lt;p&gt;There’s a class of agent actions where the cost of getting it wrong is high enough that no amount of automated validation is sufficient. Executing a SQL write against a production database. Sending an email to a thousand customers. Approving a financial transaction. Deploying code to production.&lt;/p&gt;

&lt;p&gt;For these, &lt;strong&gt;Human-in-the-Loop (HITL)&lt;/strong&gt; isn’t a limitation of your agent’s capability — it’s a deliberate architectural choice that reflects the appropriate level of trust for that action.&lt;/p&gt;

&lt;p&gt;The implementation pattern is an &lt;strong&gt;Interrupt Point&lt;/strong&gt;  — a designated node in your agent’s state graph where execution pauses, the pending action is surfaced to a human reviewer, and the agent waits for explicit approval before proceeding.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent Planning → Tool Selection → [INTERRUPT: Awaiting approval]
                                          ↓
                                   Human Reviews
                                    / \
                               Approve Reject (+ feedback)
                                  ↓ ↓
                           Agent Executes Agent Replans
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The UX of the approval interface matters more than most engineering teams acknowledge. The human reviewer needs to see: what action the agent wants to take, why it decided to take it (a brief reasoning summary), what the expected outcome is, and what the rollback plan is if it goes wrong. A one-line notification that says “Agent wants to run a database query — approve?” is not sufficient. A panel showing the exact SQL, the expected rows affected, and the agent’s stated rationale is.&lt;/p&gt;

&lt;p&gt;HITL points should be defined in configuration, not code — so that business stakeholders can adjust the approval threshold for an action type without requiring a code deploy. “Any SQL write affecting more than 1,000 rows requires approval” is a policy decision, not an engineering decision.&lt;/p&gt;

&lt;p&gt;One nuance worth designing for early: &lt;strong&gt;what happens if the human doesn’t respond?&lt;/strong&gt; The agent is waiting at an interrupt point. The reviewer is in a meeting. An hour passes. Your system needs explicit timeout behavior — either escalate to a different reviewer, cancel the task gracefully, or (for some use cases) proceed with a lower-risk fallback action. The worst outcome is an agent silently holding state and accumulating cost while waiting indefinitely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Production Reality Check
&lt;/h3&gt;

&lt;p&gt;AgentOps is the article in this series most likely to be skimmed and least likely to be implemented before launch. That’s a mistake that reliably produces avoidable incidents.&lt;/p&gt;

&lt;p&gt;Some concrete numbers to make the case:&lt;/p&gt;

&lt;p&gt;Observability instrumentation — setting up OTel, integrating LangSmith or Arize Phoenix, building a basic dashboard — takes a senior engineer approximately &lt;strong&gt;2–3 days&lt;/strong&gt; to do properly. The first production incident it prevents will typically save more than 2–3 days of debugging time, usually within the first month of operation.&lt;/p&gt;

&lt;p&gt;A well-maintained Golden Dataset with 500–1000 cases catches roughly &lt;strong&gt;60–70% of prompt-change regressions&lt;/strong&gt; before they reach production, based on experience across several production systems. The remaining 30–40% are novel failure modes — which get added to the dataset after they’re found, continuously improving the coverage.&lt;/p&gt;

&lt;p&gt;HITL approval flows feel like friction during development (“the agent should just do it”). In production, they become the feature that saves your team from the 2am incident where the agent queued 50,000 email sends based on a misconfigured trigger. Every high-stakes agentic system needs at least one HITL checkpoint. Design it in from the start — retrofitting it into an existing state machine is painful.&lt;/p&gt;

&lt;p&gt;The honest framing: every hour you invest in AgentOps before launch is worth roughly five hours of incident response after it. The math isn’t complicated. The discipline is.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Comes Next
&lt;/h3&gt;

&lt;p&gt;Article 4 is where we zoom out from how individual agents work to how &lt;em&gt;different agents talk to each other&lt;/em&gt; — across team boundaries, vendor boundaries, and trust boundaries.&lt;/p&gt;

&lt;p&gt;The Model Context Protocol (MCP) and Agent-to-Agent (A2A) communication standards are quietly becoming the connective tissue of the agentic ecosystem. If you’re building agents that need to interoperate with tools, services, or other agents you don’t control — which is almost everyone — understanding these protocols is no longer optional.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  L1: Stateless L2: Tool-Augmented L3: Autonomous L4: Multi-Agent L5: Self-Correcting
------------------------------------------------------------------------------------------------------------------------------------------------------
Execution Serverless / Edge Serverless + integr. Long-running container Distributed orchestrator Distributed + feedback loops
State None None Short + long-term memory Shared state across agents State + mutation history
Latency profile Predictable Slightly variable Variable (loop-dependent) High, parallelizable Highest, bounded by budget
Cost model Linear (tokens) Linear + tool costs Nonlinear (calls per task) Nonlinear × agent count Nonlinear × iteration count
Primary failure Bad retrieval Tool hallucination Context overflow Cascade failures Runaway loops
Observability Basic logging Tool call tracing Full trace per loop Cross-agent tracing Cost + quality dashboards
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Included for reference from &lt;a href="https://topuzas.medium.com/agentic-architectures-article-1-the-agentic-ai-maturity-model-092f009cf2c0" rel="noopener noreferrer"&gt;Article 1&lt;/a&gt; — AgentOps practices apply most critically at L3, L4, and L5.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is Article 3 of a 4-part series on Agentic AI Architectures. Article 4 — Agentic Protocols (MCP and A2A) — is the final piece.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>softwareengineering</category>
      <category>artificialintelligen</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Agentic Architectures — Article 2: Advanced Coordination and Reasoning Patterns</title>
      <dc:creator>Ali Suleyman TOPUZ</dc:creator>
      <pubDate>Sun, 29 Mar 2026 16:28:59 +0000</pubDate>
      <link>https://forem.com/topuzas/agentic-architectures-article-2-advanced-coordination-and-reasoning-patterns-36p</link>
      <guid>https://forem.com/topuzas/agentic-architectures-article-2-advanced-coordination-and-reasoning-patterns-36p</guid>
      <description>&lt;h3&gt;
  
  
  Solving the “Stochastic Parrot” Problem with Structured Logic
&lt;/h3&gt;

&lt;p&gt;There’s a criticism of large language models that has stuck around since 2021, and it still stings a little: the “stochastic parrot” argument. The idea is that LLMs are sophisticated pattern-matchers that produce statistically plausible text without any genuine understanding behind it. They’re parroting, not reasoning.&lt;/p&gt;

&lt;p&gt;I’m not here to settle that philosophical debate. What I &lt;em&gt;am&lt;/em&gt; here to tell you is this: if your agentic system &lt;em&gt;behaves&lt;/em&gt; like a stochastic parrot — confidently producing plausible-sounding but wrong answers, failing to backtrack when it hits a dead end, unable to break a hard problem into manageable pieces — the fix is almost never the model. It’s the architecture.&lt;/p&gt;

&lt;p&gt;The difference between an agent that &lt;em&gt;looks&lt;/em&gt; intelligent in a demo and one that &lt;em&gt;stays&lt;/em&gt; intelligent in production comes down to coordination and reasoning patterns. How does your agent plan? How does it check its own work? How do multiple agents share what they know without drowning each other in JSON?&lt;/p&gt;

&lt;p&gt;That’s what this article is about.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamic Planning: From Static Chains to Hierarchical Thinking
&lt;/h3&gt;

&lt;p&gt;The first generation of “agentic” products were really just dressed-up chains. You’d define a fixed sequence of LLM calls — summarize, then classify, then respond — and call it a pipeline. It worked for simple, predictable tasks. It fell apart the moment the real world showed up.&lt;/p&gt;

&lt;p&gt;Real tasks are rarely linear. A user asking &lt;em&gt;“research our top three competitors and draft a positioning document”&lt;/em&gt; doesn’t map cleanly to a fixed sequence of steps. The number of competitors might be two or five. Each competitor might require a different depth of research. The positioning document might need a complete rewrite after the research reveals something unexpected.&lt;/p&gt;

&lt;p&gt;What you need is &lt;strong&gt;Hierarchical Planning&lt;/strong&gt;  — a “Manager” agent that treats the task as a problem to be decomposed, not a script to be executed.&lt;/p&gt;

&lt;p&gt;The pattern works like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Task
    └── Manager Agent (Planner)
            ├── Sub-task A → Worker Agent 1
            ├── Sub-task B → Worker Agent 2
            └── Sub-task C → Worker Agent 3
                    └── Sub-sub-task C1 → Worker Agent 3a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Manager receives the top-level goal and produces a structured plan — a list of sub-tasks with dependencies, assigned roles, and success criteria. Worker agents execute their assigned sub-tasks and report results back. The Manager synthesizes the results, evaluates whether the goal has been met, and either delivers the final output or replans if something went wrong.&lt;/p&gt;

&lt;p&gt;The critical implementation detail that most tutorials skip: &lt;strong&gt;the plan must be a living document, not a frozen spec.&lt;/strong&gt; If Worker Agent 2 comes back with an unexpected result — say, a competitor has already pivoted out of your market — the Manager needs to update the plan in response. A Manager that rigidly executes the original plan in the face of new information isn’t planning; it’s just executing a slightly fancier chain.&lt;/p&gt;

&lt;p&gt;In practice, this means storing the plan in a mutable shared state that the Manager can read and rewrite between steps. LangGraph handles this elegantly with its state graph model. CrewAI has a more opinionated take with its hierarchical process mode. Both work — the choice depends on how much control you want over the graph structure (more on that shortly).&lt;/p&gt;

&lt;h3&gt;
  
  
  Fractal Chain-of-Thought: Reasoning That Zooms In
&lt;/h3&gt;

&lt;p&gt;Standard Chain-of-Thought prompting — “think step by step before answering” — is one of the most reliable techniques for improving LLM reasoning quality. But it has a ceiling. For deeply complex problems, a flat sequence of reasoning steps runs out of resolution. The model is reasoning about the right things at the wrong granularity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fractal Chain-of-Thought (FCoT)&lt;/strong&gt; addresses this by making reasoning recursive. When an agent encounters a sub-problem that is itself complex enough to warrant multi-step reasoning, it spawns a nested reasoning process rather than trying to resolve it in a single step.&lt;/p&gt;

&lt;p&gt;Think of it like a zoom function. The top-level reasoning operates at the problem level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Problem: Optimize our database query performance
  Step 1: Identify the slow queries
  Step 2: Analyze the execution plans
  Step 3: Propose index changes
  Step 4: Estimate performance impact
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But Step 2 — “analyze the execution plans” — is itself a multi-step reasoning problem that deserves its own chain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sub-problem: Analyze execution plan for Query #7
  Step 2.1: Identify full table scans
  Step 2.2: Check join order efficiency
  Step 2.3: Evaluate predicate pushdown opportunities
  Step 2.4: Flag missing statistics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And Step 2.3 might zoom in further still.&lt;/p&gt;

&lt;p&gt;The implementation is cleaner than it sounds. You give the agent a tool called something like deep_reason(sub_problem: str) -&amp;gt; str that recursively invokes the same reasoning architecture on the sub-problem. The result gets folded back into the parent reasoning chain. You set a maximum recursion depth (3-4 levels is usually plenty) to prevent infinite descent.&lt;/p&gt;

&lt;p&gt;The payoff is significant for domains with nested complexity — legal analysis, systems debugging, financial modeling. The cost is proportionally higher token usage. FCoT is a targeted tool, not a default setting.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Reflection Pattern: Building a Critic That Actually Criticizes
&lt;/h3&gt;

&lt;p&gt;Here’s a failure mode that bites almost every team eventually: you implement a self-review step where the same model that generated an output also reviews it. The model gives itself a pass. Every time. The “reflection” becomes a rubber stamp.&lt;/p&gt;

&lt;p&gt;This happens because LLMs are, to put it charitably, optimistic about their own work. The same statistical patterns that produced the original output will evaluate it favorably. You’ve built a conflict of interest into your architecture.&lt;/p&gt;

&lt;p&gt;The fix is the &lt;strong&gt;Critic Pattern&lt;/strong&gt; , and the key design principle is &lt;em&gt;model diversity&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Generator (Model A) → Output → Critic (Model B) → Feedback → Generator → Revised Output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using a &lt;em&gt;different&lt;/em&gt; model for the critic role — Claude reviewing GPT-4o output, or Gemini reviewing Claude output — introduces genuine perspective diversity. Each model has different training data emphases, different failure modes, and different stylistic biases. A cross-model critic is far more likely to catch errors that the generator is systematically blind to.&lt;/p&gt;

&lt;p&gt;The Critic agent should be given a structured evaluation rubric, not a vague “review this” prompt. A good rubric for a code-generating agent might look like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Correctness&lt;/strong&gt; : Does the code do what the spec requires?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge cases&lt;/strong&gt; : Are null inputs, empty collections, and boundary values handled?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt; : Are there injection vectors, exposed secrets, or unsafe deserialization?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Readability&lt;/strong&gt; : Would a mid-level engineer understand this without comments?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test coverage&lt;/strong&gt; : Are the happy path and at least two failure paths tested?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Critic returns a structured response — pass/fail per criterion, plus specific feedback for each failure. The Generator receives this structured feedback and revises. This continues until all criteria pass or the maximum iteration count is reached.&lt;/p&gt;

&lt;p&gt;One underrated implementation detail: &lt;strong&gt;give the Critic explicit permission to fail things.&lt;/strong&gt; If your Critic prompt says “review this and suggest improvements,” you’ll get suggestions. If it says “your job is to find reasons this should not ship — be adversarial,” you’ll get a real review.&lt;/p&gt;

&lt;h3&gt;
  
  
  State Machines vs. DAGs: Choosing Your Control Flow Model
&lt;/h3&gt;

&lt;p&gt;This is the question that causes more architecture debates than almost any other in the multi-agent space, and the answer is genuinely context-dependent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Directed Acyclic Graphs (DAGs)&lt;/strong&gt; model workflows that flow in one direction without cycles. Task A feeds into Task B and Task C; B and C feed into Task D; done. This is the natural model for pipelines where each step produces input for the next and you never need to revisit a completed step.&lt;/p&gt;

&lt;p&gt;CrewAI’s sequential and hierarchical processes are essentially DAG-based. Temporal workflows are explicitly DAG-structured. They’re excellent for deterministic, well-understood workflows where the shape of the computation is known in advance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cyclic graphs (State Machines)&lt;/strong&gt; allow loops — the ability to return to a previous state based on new information. This is what LangGraph was purpose-built for, and it’s the right model for any agent that needs to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retry a failed tool call with modified parameters&lt;/li&gt;
&lt;li&gt;Return to a planning step after discovering the current plan won’t work&lt;/li&gt;
&lt;li&gt;Run a reflection loop until quality criteria are met&lt;/li&gt;
&lt;li&gt;Wait for human approval before proceeding&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The decision rule I’ve converged on after shipping several production systems:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Does your agent ever need to go backwards?
                           / \
                         YES NO
                          | |
                     LangGraph CrewAI / Temporal
                  (cyclic graph) (DAG model)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;“Going backwards” means any scenario where the correct next step depends on the &lt;em&gt;outcome&lt;/em&gt; of a previous step in a way that might require revisiting earlier work. Reflection loops go backwards. Replanning goes backwards. Waiting for a human to approve and then resuming goes backwards.&lt;/p&gt;

&lt;p&gt;If your workflow is genuinely linear — always the same steps, always in the same order, with no branching based on intermediate results — a DAG model is simpler and easier to reason about. But be honest with yourself about whether your workflow is &lt;em&gt;actually&lt;/em&gt; linear or whether you’re just assuming it will be.&lt;/p&gt;

&lt;p&gt;The infrastructure implications differ significantly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  DAG Model Cyclic / State Machine
-----------------------------------------------------------------------------------------------
Execution model Step functions / pipelines Long-running stateful process
State management Passed between steps Persisted in graph state store
Debugging Linear trace, easy to follow Requires full state inspection per node
Scalability Each step independently Entire graph runs in one execution context
Failure recovery Retry from last step Checkpoint and resume from last stable state
Cost predictability High (bounded steps) Lower (loop count is variable)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Shared Epistemic Memory: The Blackboard Architecture
&lt;/h3&gt;

&lt;p&gt;Here’s a scaling problem that hits every team that gets beyond three or four agents: how do agents share what they know?&lt;/p&gt;

&lt;p&gt;The naive approach is to pass everything as function arguments — the output of Agent A becomes the input to Agent B as a large JSON blob. This works until it doesn’t. The blob grows. Context windows fill up. You start seeing agents with 80% of their context window consumed by state they don’t actually need for their specific sub-task.&lt;/p&gt;

&lt;p&gt;The sophisticated approach is the &lt;strong&gt;Blackboard Architecture&lt;/strong&gt;  — a pattern borrowed from classical AI and distributed systems that is experiencing a quiet renaissance in the agentic era.&lt;/p&gt;

&lt;p&gt;The concept is simple: instead of passing state between agents directly, all agents read from and write to a shared “blackboard” — a structured, queryable state store that sits outside any individual agent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                        ┌─────────────────┐
                        │ BLACKBOARD │
                        │ (Shared State) │
                        └────────┬────────┘
                    ┌────────────┼────────────┐
                    ↓ ↓ ↓
              Agent A Agent B Agent C
            (reads/writes) (reads/writes) (reads/writes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each agent reads only the sections of the blackboard relevant to its current task. Each agent writes its outputs back to designated sections. No agent needs to know what other agents are doing — it just needs to know the schema of the blackboard.&lt;/p&gt;

&lt;p&gt;In practice, the blackboard is typically implemented as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;structured document&lt;/strong&gt; in a database (DynamoDB, Redis, or Postgres with JSONB) for fast key-based access to specific state sections&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;vector store&lt;/strong&gt; for semantic retrieval when agents need to find relevant context without knowing the exact key&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;message log&lt;/strong&gt; for ordered history that agents can replay or summarize&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The schema design of your blackboard is one of the most important architectural decisions you’ll make. Too flat and agents can’t find what they need without reading everything. Too nested and updates become complex. A layered approach works well: top-level sections for task metadata, agent outputs, shared knowledge, and execution history.&lt;/p&gt;

&lt;p&gt;One design principle worth emphasizing: &lt;strong&gt;write provenance into every blackboard entry.&lt;/strong&gt; Every piece of information written to the shared state should include which agent wrote it, when, and with what confidence level. When a downstream agent reads a fact and makes a decision based on it, you want to be able to trace that decision back to its source when something goes wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  Production Reality Check
&lt;/h3&gt;

&lt;p&gt;The patterns in this article are genuinely powerful. They’re also genuinely expensive, and the cost compounds in ways that aren’t obvious until you’re staring at a cloud bill.&lt;/p&gt;

&lt;p&gt;Let’s put some real numbers on it.&lt;/p&gt;

&lt;p&gt;A Reflection loop that runs an average of 2 iterations doubles your model call count. If your base cost is $0.05 per task, reflection takes it to $0.10. That sounds manageable — until you’re handling 50,000 tasks per day, at which point reflection alone adds $2,500/day in model costs.&lt;/p&gt;

&lt;p&gt;Fractal Chain-of-Thought with 3 levels of recursion and an average of 4 steps per level generates roughly 64 reasoning steps (⁴³) for a single complex query. At even modest token counts per step, this can push a single query cost into the $0.50–$2.00 range. Reserve it for problems that actually need that depth.&lt;/p&gt;

&lt;p&gt;Cross-model Critic patterns (e.g., Claude reviewing GPT-4o) introduce a second API dependency with its own latency, rate limits, and cost curve. Budget for both. More importantly, test what happens when the Critic’s API goes down — your system should degrade gracefully, not grind to a halt.&lt;/p&gt;

&lt;p&gt;The honest question to ask before adding any coordination pattern is: &lt;strong&gt;what’s the quality delta, and what’s it worth?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Reflection improving accuracy from 78% to 91% on a customer-facing recommendation engine that drives revenue? Worth the cost. Reflection improving accuracy from 94% to 96% on an internal summarization tool that saves analysts 10 minutes a day? Probably not.&lt;/p&gt;

&lt;p&gt;Measure first. Add complexity second.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Comes Next
&lt;/h3&gt;

&lt;p&gt;In Article 3, we shift from &lt;em&gt;how agents think&lt;/em&gt; to &lt;em&gt;how agents fail&lt;/em&gt; — and more importantly, how you detect and recover from those failures before your users do.&lt;/p&gt;

&lt;p&gt;AgentOps is the unglamorous but absolutely essential discipline of treating your AI system like the distributed system it actually is: with observability, guardrails, eval pipelines, and human checkpoints baked in from the start. Not bolted on after the first production incident.&lt;/p&gt;

&lt;p&gt;Because there will be a production incident. There always is.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  L1: Stateless L2: Tool-Augmented L3: Autonomous L4: Multi-Agent L5: Self-Correcting
------------------------------------------------------------------------------------------------------------------------------------------------------
Execution Serverless / Edge Serverless + integr. Long-running container Distributed orchestrator Distributed + feedback loops
State None None Short + long-term memory Shared state across agents State + mutation history
Latency profile Predictable Slightly variable Variable (loop-dependent) High, parallelizable Highest, bounded by budget
Cost model Linear (tokens) Linear + tool costs Nonlinear (calls per task) Nonlinear × agent count Nonlinear × iteration count
Primary failure Bad retrieval Tool hallucination Context overflow Cascade failures Runaway loops
Observability Basic logging Tool call tracing Full trace per loop Cross-agent tracing Cost + quality dashboards
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Included for reference from &lt;a href="https://topuzas.medium.com/agentic-architectures-article-1-the-agentic-ai-maturity-model-092f009cf2c0" rel="noopener noreferrer"&gt;Article 1 &lt;/a&gt;— the coordination patterns in this article map primarily to L3, L4, and L5.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is Article 2 of a 4-part series on Agentic AI Architectures. Ready for Article 3 — AgentOps — whenever you are.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>machinelearning</category>
      <category>softwareengineering</category>
      <category>artificialintelligen</category>
    </item>
    <item>
      <title>Agentic Architectures — Article 1: The Agentic AI Maturity Model</title>
      <dc:creator>Ali Suleyman TOPUZ</dc:creator>
      <pubDate>Sun, 29 Mar 2026 16:28:25 +0000</pubDate>
      <link>https://forem.com/topuzas/agentic-architectures-article-1-the-agentic-ai-maturity-model-47pl</link>
      <guid>https://forem.com/topuzas/agentic-architectures-article-1-the-agentic-ai-maturity-model-47pl</guid>
      <description>&lt;h3&gt;
  
  
  From “Just Call the API” to Self-Evolving Ecosystems
&lt;/h3&gt;

&lt;p&gt;There’s a conversation I keep having with engineering teams. Someone has just shipped a feature that calls GPT-4o or Claude, the demo looks impressive, and then a product manager walks in and asks: &lt;em&gt;“So when do we make it fully autonomous?”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The room goes quiet.&lt;/p&gt;

&lt;p&gt;The problem isn’t ambition — it’s vocabulary. “Autonomous” means five completely different things depending on who’s in the room. To the CTO, it means cost savings. To the ML engineer, it means ReAct loops and tool-calling. To the backend team, it means a distributed system they’re going to have to debug at 2am.&lt;/p&gt;

&lt;p&gt;What we need is a shared language. A maturity model.&lt;/p&gt;

&lt;p&gt;I’ve spent the last two years building production AI systems — RAG pipelines, multi-agent orchestrators, agentic workflows running on cloud runtimes — and I’ve come to believe that every system you build sits at one of five levels. Knowing which level you’re on is the single most important thing you can do before making architectural decisions.&lt;/p&gt;

&lt;p&gt;Let’s walk through all five.&lt;/p&gt;

&lt;h3&gt;
  
  
  Level 1: Prompt-Based — The Stateless
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The signature move:&lt;/strong&gt; You write a prompt. The model responds. Done.&lt;/p&gt;

&lt;p&gt;This is where every team starts, and there’s no shame in it. A well-engineered Level 1 system — think basic RAG with a vector database, or a single-turn LLM call wrapped in a clean API — can handle an enormous amount of real business value. Customer FAQ bots, document summarization, code explanation tools: these are Level 1, and they work.&lt;/p&gt;

&lt;p&gt;The architecture is simple because the state is zero. Each request is born and dies in a single HTTP round-trip. There’s no memory between turns, no planning, no tool use. The LLM is a sophisticated function: input goes in, text comes out.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Query → [Context Retrieval] → Prompt → LLM → Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Infrastructure fingerprint:&lt;/strong&gt; A single serverless function (Lambda, Cloud Run) is often enough. Latency is predictable because you’re making exactly one model call. Cost is linear and easy to forecast. The main failure mode is retrieval quality — garbage in, garbage out — not the agent layer, because there is no agent layer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it breaks:&lt;/strong&gt; The moment a user wants the system to &lt;em&gt;do&lt;/em&gt; something with the answer. “Summarize this contract” is Level 1. “Summarize this contract and then send the action items to our Jira board” is not.&lt;/p&gt;

&lt;h3&gt;
  
  
  Level 2: Tool-Augmented — The Doer
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The signature move:&lt;/strong&gt; The model decides &lt;em&gt;which function to call&lt;/em&gt;, and your infrastructure executes it.&lt;/p&gt;

&lt;p&gt;This is where things get genuinely interesting — and where a surprising number of teams stop, thinking they’ve “done AI.” Function calling (or “tool use” in Anthropic’s terminology) fundamentally changes the mental model. The LLM is no longer just generating text; it’s generating &lt;em&gt;intent&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;You define a set of tools — an OpenAPI spec, a Python function schema, a list of MCP-compatible endpoints — and the model figures out which ones to invoke based on the user’s request. Your code handles the execution and feeds the result back.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Query → LLM (reasoning) → Tool Call → Execution → LLM (synthesis) → Response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What makes Level 2 non-trivial in production is error handling. Models hallucinate tool names. They pass arguments with wrong types. They call a write endpoint when they should have called a read one. A robust Level 2 system needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Input validation&lt;/strong&gt; on every tool call before execution&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graceful fallbacks&lt;/strong&gt; when a tool returns an error (don’t just crash — tell the model what went wrong and let it retry)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idempotency checks&lt;/strong&gt; on any tool that mutates state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The OpenAPI spec integration story is particularly powerful here. If you describe your internal APIs in OpenAPI format, you can essentially give the model a self-describing interface to your entire backend. This is the beating heart of products like Copilot for enterprise apps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure fingerprint:&lt;/strong&gt; You’re now managing tool execution latency in addition to model latency. Two or three tool calls in sequence, each taking 200–500ms, can make a “fast” response feel slow. Start thinking about parallelizing independent tool calls. Cost starts to diverge from simple per-token math — a tool that calls a third-party API has its own cost curve.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it breaks:&lt;/strong&gt; When the task requires multi-step reasoning across &lt;em&gt;interdependent&lt;/em&gt; actions. The model can call tools, but it can’t hold a plan in its head across a long sequence of them. For that, you need state.&lt;/p&gt;

&lt;h3&gt;
  
  
  Level 3: Autonomous Agents — The Planner
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The signature move:&lt;/strong&gt; The ReAct loop. Reason, Act, Observe, repeat.&lt;/p&gt;

&lt;p&gt;This is the architecture that the word “agent” was coined for. Introduced in the landmark 2022 paper &lt;em&gt;ReAct: Synergizing Reasoning and Acting in Language Models&lt;/em&gt;, the core idea is elegantly simple: instead of a single prompt-response cycle, you give the model a loop.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Thought → Action → Observation → Thought → Action → Observation → ... → Final Answer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At each step, the model articulates its reasoning (“I need to check the user’s account balance before proceeding”), selects a tool, observes the result, and decides what to do next. The loop continues until the model decides it has enough information to respond.&lt;/p&gt;

&lt;p&gt;What makes Level 3 qualitatively different from Level 2 is &lt;strong&gt;memory management&lt;/strong&gt;. A ReAct agent needs to track what it’s done, what it’s learned, and what it still needs to do. This splits into two distinct concerns:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Short-term memory&lt;/strong&gt; is the conversation context — the running thread of thoughts, actions, and observations that constitutes the current task. In practice, this is the LLM’s context window, and it’s finite. Naive implementations stuff everything into the context until it overflows. Sophisticated ones implement sliding windows, summarization, or structured scratchpads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Long-term memory&lt;/strong&gt; is everything the agent needs to remember &lt;em&gt;across&lt;/em&gt; tasks — user preferences, learned facts, past decisions. This typically lives outside the model entirely: a vector database for semantic retrieval, a key-value store for structured facts, or a graph database for relational knowledge.&lt;/p&gt;

&lt;p&gt;The combination of a reasoning loop and dual-layer memory is what gives Level 3 agents their apparent intelligence. They can decompose problems, backtrack when a tool call fails, and accumulate knowledge over a session in ways that feel remarkably human.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure fingerprint:&lt;/strong&gt; Now you’re operating a stateful, long-running process. Serverless functions with 30-second timeouts don’t cut it anymore. You need persistent execution environments — containerized long-running services, step function orchestrators, or purpose-built agent runtimes (AWS Bedrock AgentCore, Azure AI Foundry Agent Service). Token costs are no longer linear: a complex reasoning chain might make 8–12 model calls to answer one user query. Build cost monitoring from day one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it breaks:&lt;/strong&gt; A single agent with access to all tools is a single point of failure — and a single point of security exposure. When the task requires genuine parallelism or specialist expertise, one planner isn’t enough.&lt;/p&gt;

&lt;h3&gt;
  
  
  Level 4: Multi-Agent Orchestration — The Team
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The signature move:&lt;/strong&gt; Specialized agents with defined roles, coordinated by an orchestrator.&lt;/p&gt;

&lt;p&gt;The intuition here maps cleanly to how human teams work. You don’t hire one person who is simultaneously a senior engineer, a QA lead, a security auditor, and a product manager. You build a team. Level 4 applies the same logic to AI.&lt;/p&gt;

&lt;p&gt;A canonical software engineering multi-agent system might look like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Orchestrator Agent&lt;/strong&gt; : Receives the task, breaks it into sub-tasks, routes work, and assembles the final output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coder Agent&lt;/strong&gt; : Writes code given a spec. Has access to file system tools and a code execution sandbox.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reviewer Agent&lt;/strong&gt; : Reads code, applies a checklist, and returns structured feedback. Possibly runs on a different model for perspective diversity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tester Agent&lt;/strong&gt; : Generates test cases, runs them against the code, and reports pass/fail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security Agent&lt;/strong&gt; : Scans for common vulnerabilities (injection, exposed secrets) before the code is merged.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each agent operates within a narrow, well-defined context. This matters for three reasons:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reduced hallucination:&lt;/strong&gt; A focused prompt with a specific role and limited tool access produces more reliable output than a general-purpose agent trying to do everything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parallelism:&lt;/strong&gt; Independent sub-tasks can run concurrently. The Reviewer and Tester can work in parallel on the same code diff.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accountability:&lt;/strong&gt; When something goes wrong (and it will), you can isolate &lt;em&gt;which&lt;/em&gt; agent in the pipeline failed and why. This is far easier than debugging a single monolithic agent’s 40-step reasoning trace.&lt;/p&gt;

&lt;p&gt;The coordination layer is where the real engineering lives. You need to decide how agents communicate — direct calls, a message queue, a shared state store — and how to handle failures in one agent without cascading across the whole system. (More on this in Article 2.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure fingerprint:&lt;/strong&gt; You are now running a distributed system. All of the distributed systems problems apply: network partitions, partial failures, ordering guarantees, idempotency. Your observability stack needs to trace requests &lt;em&gt;across&lt;/em&gt; agents, not just within one. Tools like LangSmith or Arize Phoenix become essential, not optional. Compute costs grow non-linearly with agent count — a four-agent pipeline that each makes 5 model calls generates 20 model calls per user request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where it breaks:&lt;/strong&gt; Quality drift. Multi-agent systems can converge on confidently wrong answers because each agent assumes the previous one got it right. No one is questioning the chain. That’s the job of Level 5.&lt;/p&gt;

&lt;h3&gt;
  
  
  Level 5: Self-Correcting Systems — The Optimizer
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The signature move:&lt;/strong&gt; Agents that critique their own output and update their own behavior.&lt;/p&gt;

&lt;p&gt;This is the frontier — and the most misunderstood level. “Self-correcting” doesn’t mean the AI is rewriting its own weights (that’s training, not inference). It means the system has architectural mechanisms to catch its own errors and improve its outputs &lt;em&gt;within a deployment&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The foundational pattern is &lt;strong&gt;Reflection&lt;/strong&gt;. After an agent produces an output, a separate “Critic” agent (or a second pass of the same model with a different prompt) evaluates it against a rubric:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does this answer the original question?&lt;/li&gt;
&lt;li&gt;Are there factual claims that need verification?&lt;/li&gt;
&lt;li&gt;Does the code actually compile and pass tests?&lt;/li&gt;
&lt;li&gt;Is the tone appropriate for the context?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the critic finds problems, the output goes back to the generator with structured feedback. The generator revises. The critic reviews again. This loop runs until the output passes — or until a maximum iteration count is hit (always set one; an infinite reflection loop is a runaway cost event).&lt;/p&gt;

&lt;p&gt;The more advanced form of this is &lt;strong&gt;prompt mutation&lt;/strong&gt;  — when an agent not only fixes its current output but also updates the prompt template that produced it, so future calls start from a better baseline. This is where you start to see systems that genuinely improve over time without retraining.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Generator → Output → Critic → [Pass] → Deliver
                             → [Fail] → Feedback → Generator (repeat)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some teams implement this with dedicated frameworks (DSPy’s prompt optimization is a notable example). Others build it manually by storing “lessons learned” in long-term memory that gets injected into future prompts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure fingerprint:&lt;/strong&gt; The cost profile becomes unpredictable in a way that requires active management. A single reflection loop doubles your model calls. Two loops quadruples them. You need circuit breakers — hard limits on loop iterations, cost caps per request, and alerting when a task is taking 3x the expected token budget. The compute requirement is also asymmetric: reflection runs well on smaller models (you don’t need GPT-4o to critique a GPT-4o output; Claude 3.5 Haiku reviewing a Sonnet output can work remarkably well and costs a fraction of the alternative).&lt;/p&gt;

&lt;h3&gt;
  
  
  Mapping Levels to Infrastructure
&lt;/h3&gt;

&lt;p&gt;A quick reference for the architectural decisions that change at each level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  L1: Stateless L2: Tool-Augmented L3: Autonomous L4: Multi-Agent L5: Self-Correcting
------------------------------------------------------------------------------------------------------------------------------------------------------
Execution Serverless / Edge Serverless + integr. Long-running container Distributed orchestrator Distributed + feedback loops
State None None Short + long-term memory Shared state across agents State + mutation history
Latency profile Predictable Slightly variable Variable (loop-dependent) High, parallelizable Highest, bounded by budget
Cost model Linear (tokens) Linear + tool costs Nonlinear (calls per task) Nonlinear × agent count Nonlinear × iteration count
Primary failure Bad retrieval Tool hallucination Context overflow Cascade failures Runaway loops
Observability Basic logging Tool call tracing Full trace per loop Cross-agent tracing Cost + quality dashboards
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Production Reality Check
&lt;/h3&gt;

&lt;p&gt;Here’s the honest conversation you need to have before choosing a level: &lt;strong&gt;most production systems should be Level 2 or 3, and that’s not a failure.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I’ve seen teams build Level 4 multi-agent systems because it felt more impressive, only to discover that a well-engineered Level 2 system with good tool design would have answered 80% of the queries faster, cheaper, and with fewer failure modes.&lt;/p&gt;

&lt;p&gt;The maturity model isn’t a ladder you’re supposed to climb as fast as possible. It’s a map. The right level is the one where the &lt;em&gt;complexity you’re adding&lt;/em&gt; is justified by the &lt;em&gt;capability you’re gaining&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Some honest benchmarks from production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Level 3 ReAct agent making 8 model calls to answer a single query costs roughly &lt;strong&gt;8–15x&lt;/strong&gt; more than a Level 1 RAG call. The accuracy improvement is real — but measure it against your actual use case, not a benchmark.&lt;/li&gt;
&lt;li&gt;Adding a reflection loop (Level 5 element) to a Level 3 agent typically improves output quality by 15–30% on complex reasoning tasks. It also &lt;strong&gt;doubles latency and cost&lt;/strong&gt;. For a customer-facing product with a 3-second SLA expectation, that tradeoff often doesn’t pass.&lt;/li&gt;
&lt;li&gt;Multi-agent systems (Level 4) have an operational overhead that is consistently underestimated. Plan for it to take &lt;strong&gt;3–4x longer&lt;/strong&gt; to debug a failure in a 4-agent pipeline than in a single agent — not because the problem is harder, but because the trace is longer and the failure point is further from where the error surfaces.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What Comes Next
&lt;/h3&gt;

&lt;p&gt;In the next article, we’ll go deeper into the coordination and reasoning patterns that make Level 3 and Level 4 systems actually work in practice — hierarchical planning, the Critic architecture, and the surprisingly important question of whether you should use a cyclic graph or a DAG to model your agent’s workflow.&lt;/p&gt;

&lt;p&gt;The short answer is: it depends on whether your agent ever needs to go backwards. And the answer is almost always yes.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is Article 1 of a 4-part series on Agentic AI Architectures. The series covers the Maturity Model, Coordination &amp;amp; Reasoning Patterns, AgentOps, and Agentic Protocols (MCP &amp;amp; A2A).&lt;/em&gt;&lt;/p&gt;

</description>
      <category>artificialintelligen</category>
      <category>machinelearning</category>
      <category>softwareengineering</category>
      <category>agenticai</category>
    </item>
    <item>
      <title>Semantic Kernel for Enterprise AI: Architecting Production-Grade LLM Integration in .NET</title>
      <dc:creator>Ali Suleyman TOPUZ</dc:creator>
      <pubDate>Sat, 21 Mar 2026 18:47:46 +0000</pubDate>
      <link>https://forem.com/topuzas/semantic-kernel-for-enterprise-ai-architecting-production-grade-llm-integration-in-net-5aa4</link>
      <guid>https://forem.com/topuzas/semantic-kernel-for-enterprise-ai-architecting-production-grade-llm-integration-in-net-5aa4</guid>
      <description>&lt;h3&gt;
  
  
  Semantic Kernel for Enterprise AI: Architecting Production-Grade LLM Integration in .NET — Implementation &amp;amp; Observability — Part 2
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;This is Part 2 of the series. Part 1 covered the foundational architecture of Semantic Kernel — plugins, planners, memory, and filters — along with the FinOps cost model and SRE failure taxonomy. In this part, we move from architecture to implementation: building the async-first parallel orchestration engine, the Redis-backed semantic cache, and the complete production filter pipeline with token metering.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  I. Recap and What This Part Covers
&lt;/h3&gt;

&lt;p&gt;Part 1 established that the gap between LLM demo and production system is architectural. Semantic Kernel closes that gap through four composable primitives — plugins, planners, memory, and filters — wrapped in a resilience and observability model that matches enterprise operational standards.&lt;/p&gt;

&lt;p&gt;Part 2 builds on that foundation with concrete, production-ready .NET 9.0 implementations of the three highest-leverage components an architect must get right:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Async-First Parallel Orchestration&lt;/strong&gt;  — collapsing multi-step LLM workflows from sequential to concurrent execution, with proper cancellation, error isolation, and result aggregation patterns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Redis Semantic Cache&lt;/strong&gt;  — a vector similarity-backed caching layer that achieves 30–60% token cost reduction in enterprise workloads, with TTL management, cache invalidation, and hit-rate instrumentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Complete Filter Pipeline&lt;/strong&gt;  — the full middleware chain covering rate limiting, semantic caching, audit logging, output validation, and token metering, wired together as a coherent operational stack.&lt;/p&gt;

&lt;h3&gt;
  
  
  II. Async-First Parallel Orchestration
&lt;/h3&gt;

&lt;h3&gt;
  
  
  2.1 The Sequential Trap
&lt;/h3&gt;

&lt;p&gt;The most common performance anti-pattern in Semantic Kernel implementations is inadvertent sequential LLM invocation. Engineers familiar with async/await in .NET often write code that looks asynchronous but executes serially:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Anti-pattern: Awaiting each invocation sequentially&lt;/span&gt;
&lt;span class="c1"&gt;// Total latency = sum of all individual latencies&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DocumentAnalysisResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;AnalyzeDocumentAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;documentId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InvokeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DocumentPlugin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Summarize"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 2.1s&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;entities&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InvokeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DocumentPlugin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ExtractEntities"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 1.8s&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sentiment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InvokeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DocumentPlugin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"AnalyzeSentiment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 1.4s&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;keywords&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InvokeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DocumentPlugin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ExtractKeywords"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 1.2s&lt;/span&gt;

    &lt;span class="c1"&gt;// Total: ~6.5s — user is waiting for sequential completion&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;BuildResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entities&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sentiment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keywords&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;Four independent LLM calls executed sequentially when they have no data dependencies on each other. This is a 4× latency penalty and a FinOps problem — the user session holds open a server thread for the full duration, limiting throughput.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.2 The Parallel Orchestration Pattern
&lt;/h3&gt;

&lt;p&gt;The correct pattern treats independent LLM invocations as parallel tasks, combining Task.WhenAll with proper cancellation token propagation and isolated error handling per invocation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ✅ Parallel orchestration with error isolation&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ParallelDocumentAnalysisOrchestrator&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Kernel&lt;/span&gt; &lt;span class="n"&gt;_kernel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ParallelDocumentAnalysisOrchestrator&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ParallelOrchestrationOptions&lt;/span&gt; &lt;span class="n"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DocumentAnalysisResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;AnalyzeDocumentAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;documentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;LoadDocumentAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sharedArguments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;KernelArguments&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"document_content"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;document&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;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"document_id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;documentId&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="c1"&gt;// Define parallel execution units with individual timeout budgets&lt;/span&gt;
        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CancellationTokenSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateLinkedTokenSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;cts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CancelAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxOrchestrationDuration&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Hard timeout for entire operation&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;summaryTask&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;InvokeWithTimeoutAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"DocumentPlugin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Summarize"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sharedArguments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cts&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;entityTask&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;InvokeWithTimeoutAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"DocumentPlugin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ExtractEntities"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sharedArguments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cts&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sentimentTask&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;InvokeWithTimeoutAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"DocumentPlugin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"AnalyzeSentiment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sharedArguments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cts&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;keywordTask&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;InvokeWithTimeoutAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"DocumentPlugin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ExtractKeywords"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sharedArguments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cts&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="c1"&gt;// WhenAll preserves individual task exceptions-don't use WaitAll&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WhenAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;summaryTask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entityTask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sentimentTask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;keywordTask&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// Total latency = slowest individual invocation (~2.1s vs 6.5s sequential)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;BuildResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrchestrationResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;InvokeWithTimeoutAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;pluginName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;KernelArguments&lt;/span&gt; &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;TimeSpan&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;timeoutCts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CancellationTokenSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateLinkedTokenSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;timeoutCts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CancelAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InvokeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;pluginName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeoutCts&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;OrchestrationResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;pluginName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;functionName&lt;/span&gt;&lt;span class="p"&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;GetValue&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()!);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OperationCanceledException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsCancellationRequested&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Individual invocation timed out-don't cancel sibling tasks&lt;/span&gt;
            &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogWarning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"Function {Plugin}.{Function} exceeded timeout of {Timeout}ms"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;pluginName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalMilliseconds&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;OrchestrationResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TimedOut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pluginName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Isolate failure-sibling tasks continue executing&lt;/span&gt;
            &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s"&gt;"Function {Plugin}.{Function} failed with exception"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;pluginName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;OrchestrationResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Failed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pluginName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.3 Dependency-Aware DAG Orchestration
&lt;/h3&gt;

&lt;p&gt;Real-world workflows are rarely fully parallel. Some steps depend on the output of prior steps, creating a directed acyclic graph (DAG) of dependencies. The architectural pattern for this is staged parallel execution — group independent operations into waves, execute each wave in parallel, and feed outputs forward to dependent stages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DagOrchestrator&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Kernel&lt;/span&gt; &lt;span class="n"&gt;_kernel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;summary&amp;gt;&lt;/span&gt;
    &lt;span class="c1"&gt;/// Contract Review Workflow DAG:&lt;/span&gt;
    &lt;span class="c1"&gt;///&lt;/span&gt;
    &lt;span class="c1"&gt;/// Stage 1 (parallel): ExtractParties, ExtractDates, ExtractObligations&lt;/span&gt;
    &lt;span class="c1"&gt;/// ↓&lt;/span&gt;
    &lt;span class="c1"&gt;/// Stage 2 (parallel, depends on Stage 1): &lt;/span&gt;
    &lt;span class="c1"&gt;/// ValidateParties(parties), CheckDeadlines(dates), PrioritizeObligations(obligations)&lt;/span&gt;
    &lt;span class="c1"&gt;/// ↓&lt;/span&gt;
    &lt;span class="c1"&gt;/// Stage 3 (sequential, depends on Stage 2): GenerateExecutiveSummary(all Stage 2 outputs)&lt;/span&gt;
    &lt;span class="c1"&gt;/// &amp;lt;/summary&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ContractReviewResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ReviewContractAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;contractText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ── Stage 1: Independent extraction (parallel) ──────────────────────────&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;stage1Args&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;KernelArguments&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"contract_text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contractText&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parties&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dates&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obligations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ExecuteStageAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"LegalPlugin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ExtractParties"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stage1Args&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"LegalPlugin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ExtractDates"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stage1Args&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"LegalPlugin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ExtractObligations"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stage1Args&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="c1"&gt;// ── Stage 2: Validation (parallel, consumes Stage 1) ────────────────────&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;validatedParties&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;deadlineAnalysis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prioritizedObligations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; 
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;ExecuteStageAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"LegalPlugin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ValidateParties"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                    &lt;span class="nf"&gt;BuildArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stage1Args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"parties_json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parties&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"LegalPlugin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"CheckDeadlines"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                    &lt;span class="nf"&gt;BuildArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stage1Args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"dates_json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dates&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"LegalPlugin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"PrioritizeObligations"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                    &lt;span class="nf"&gt;BuildArgs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stage1Args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"obligations_json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obligations&lt;/span&gt;&lt;span class="p"&gt;))));&lt;/span&gt;
        &lt;span class="c1"&gt;// ── Stage 3: Synthesis (sequential, consumes all prior stages) ───────────&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;summaryArgs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;KernelArguments&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"contract_text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contractText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"validated_parties"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;validatedParties&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"deadline_analysis"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;deadlineAnalysis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"prioritized_obligations"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prioritizedObligations&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;executiveSummary&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InvokeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"LegalPlugin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"GenerateExecutiveSummary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;summaryArgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ContractReviewResult&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Parties&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;DeserializeParties&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;validatedParties&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;DeadlineAnalysis&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;DeserializeDeadlines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deadlineAnalysis&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;Obligations&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;DeserializeObligations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prioritizedObligations&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;ExecutiveSummary&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executiveSummary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetValue&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;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;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ExecuteStageAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;params&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Plugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;KernelArguments&lt;/span&gt; &lt;span class="n"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;)[]&lt;/span&gt; &lt;span class="n"&gt;invocations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;invocations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inv&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;_kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InvokeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Plugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ContinueWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&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;GetValue&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()!,&lt;/span&gt; 
                    &lt;span class="n"&gt;TaskContinuationOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OnlyOnRanToCompletion&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WhenAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.4 Streaming with Backpressure
&lt;/h3&gt;

&lt;p&gt;For user-facing operations where progressive disclosure is preferable to waiting for full completion, GetStreamingChatMessageContentsAsync paired with server-sent events or SignalR delivers tokens to the UI as they arrive. The critical implementation detail is proper backpressure — don't buffer unboundedly if downstream consumers are slower than the LLM's generation rate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StreamingOrchestrator&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Kernel&lt;/span&gt; &lt;span class="n"&gt;_kernel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;IAsyncEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;StreamAnalysisAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;documentContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;EnumeratorCancellation&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;arguments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;KernelArguments&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;documentContent&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;executionSettings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;OpenAIPromptExecutionSettings&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;MaxTokens&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Temperature&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="c1"&gt;// Stream-specific: stop generation when we have enough for the UI&lt;/span&gt;
            &lt;span class="n"&gt;StopSequences&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"[END_SUMMARY]"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExecutionSettings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PromptExecutionSettings&lt;/span&gt;&lt;span class="p"&gt;&amp;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;PromptExecutionSettings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultServiceId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;executionSettings&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tokenBuffer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StringBuilder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tokenCount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_kernel&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvokeStreamingAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;StreamingChatMessageContent&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"AnalysisPlugin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"StreamSummary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;tokenBuffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&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;span class="n"&gt;tokenCount&lt;/span&gt;&lt;span class="p"&gt;++;&lt;/span&gt;
            &lt;span class="c1"&gt;// Yield complete words/sentences rather than individual tokens&lt;/span&gt;
            &lt;span class="c1"&gt;// to reduce UI flicker and downstream rendering load&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&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;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&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;chunk&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;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'\n'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tokenBuffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="n"&gt;tokenBuffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="c1"&gt;// Early termination: if we've generated enough for the use case,&lt;/span&gt;
            &lt;span class="c1"&gt;// cancel further generation to avoid paying for unused completion tokens&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokenCount&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;800&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tokenBuffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;break&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="c1"&gt;// Flush any remaining buffered content&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokenBuffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tokenBuffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  III. Redis Semantic Cache Implementation
&lt;/h3&gt;

&lt;h3&gt;
  
  
  3.1 Why String Equality Is the Wrong Cache Key
&lt;/h3&gt;

&lt;p&gt;Naive caching uses exact string matching: two requests are equivalent if their prompt strings are byte-for-byte identical. This produces near-zero cache hit rates in practice because natural language users ask the same question in slightly different ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Summarize this contract”&lt;/li&gt;
&lt;li&gt;“Give me a summary of this contract”&lt;/li&gt;
&lt;li&gt;“What does this contract say, briefly?”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are semantically equivalent — they should return the same cached response. String equality misses all three matches.&lt;/p&gt;

&lt;p&gt;Semantic caching uses vector embeddings to measure intent similarity. Two prompts are considered equivalent if their embedding vectors are within a configurable cosine similarity threshold. This is what drives the 30–60% cache hit rates referenced in Part 1.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.2 Redis Stack Setup and Index Configuration
&lt;/h3&gt;

&lt;p&gt;RedisStack (available in Azure Cache for Redis Enterprise) provides the FT.SEARCH capability with vector similarity search. The index configuration determines both search performance and accuracy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RedisSemanticCacheService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ISemanticCacheService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IAsyncDisposable&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IConnectionMultiplexer&lt;/span&gt; &lt;span class="n"&gt;_redis&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IDatabase&lt;/span&gt; &lt;span class="n"&gt;_db&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ITextEmbeddingGenerationService&lt;/span&gt; &lt;span class="n"&gt;_embeddingService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;SemanticCacheConfiguration&lt;/span&gt; &lt;span class="n"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RedisSemanticCacheService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;IndexName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"semantic-cache-idx"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;KeyPrefix&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"sk-cache:"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;RedisSemanticCacheService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;IConnectionMultiplexer&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ITextEmbeddingGenerationService&lt;/span&gt; &lt;span class="n"&gt;embeddingService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;SemanticCacheConfiguration&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RedisSemanticCacheService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_redis&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_db&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetDatabase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;_embeddingService&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;embeddingService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_config&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_logger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;InitializeIndexAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetEndPoints&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;First&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Check if index already exists&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"FT.INFO"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IndexName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Semantic cache index {IndexName} already exists"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IndexName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RedisServerException&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&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="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Unknown index name"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Create the vector index&lt;/span&gt;
            &lt;span class="c1"&gt;// HNSW (Hierarchical Navigable Small World) is the recommended algorithm&lt;/span&gt;
            &lt;span class="c1"&gt;// for production-better query performance than FLAT at scale&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"FT.CREATE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IndexName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s"&gt;"ON"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"HASH"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s"&gt;"PREFIX"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;KeyPrefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s"&gt;"SCHEMA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s"&gt;"plugin_name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"TAG"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s"&gt;"function_name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"TAG"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s"&gt;"tenant_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"TAG"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s"&gt;"embedding"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"VECTOR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"HNSW"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"10"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="s"&gt;"TYPE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"FLOAT32"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="s"&gt;"DIM"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"1536"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// text-embedding-3-small dimension&lt;/span&gt;
                        &lt;span class="s"&gt;"DISTANCE_METRIC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"COSINE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="s"&gt;"INITIAL_CAP"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"10000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="s"&gt;"EF_CONSTRUCTION"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"200"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Higher = better quality, slower build&lt;/span&gt;
                    &lt;span class="s"&gt;"response_text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"TEXT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"NUMERIC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s"&gt;"hit_count"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"NUMERIC"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"Created semantic cache index {IndexName} with HNSW vector configuration"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;IndexName&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;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SemanticCacheEntry&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;pluginName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;promptText&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_embeddingService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GenerateEmbeddingAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;promptText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;embeddingBytes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;EmbeddingToBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// KNN vector search with metadata filter&lt;/span&gt;
        &lt;span class="c1"&gt;// The TAG filters narrow the search space before vector comparison&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"(@plugin_name:&lt;/span&gt;&lt;span class="p"&gt;{{{&lt;/span&gt;&lt;span class="nf"&gt;EscapeTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pluginName&lt;/span&gt;&lt;span class="p"&gt;)}}}&lt;/span&gt;&lt;span class="s"&gt; "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
                    &lt;span class="s"&gt;$"@function_name:&lt;/span&gt;&lt;span class="p"&gt;{{{&lt;/span&gt;&lt;span class="nf"&gt;EscapeTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;)}}}&lt;/span&gt;&lt;span class="s"&gt; "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
                    &lt;span class="s"&gt;$"@tenant_id:&lt;/span&gt;&lt;span class="p"&gt;{{{&lt;/span&gt;&lt;span class="nf"&gt;EscapeTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;)}}}&lt;/span&gt;&lt;span class="s"&gt;)=&amp;gt;"&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
                    &lt;span class="s"&gt;$"[KNN &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxCandidates&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; @embedding $vec AS score]"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;searchResults&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"FT.SEARCH"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IndexName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"PARAMS"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"vec"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embeddingBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"RETURN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"response_text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"hit_count"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"SORTBY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"DIALECT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&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;ParseSearchResults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;searchResults&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&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;Any&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogDebug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"Cache miss for {Plugin}.{Function} (no candidates found)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;pluginName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;best&lt;/span&gt; &lt;span class="p"&gt;=&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;First&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="c1"&gt;// Cosine similarity: score 0 = identical, score 1 = orthogonal&lt;/span&gt;
        &lt;span class="c1"&gt;// We want high similarity, so threshold is checked as (1 - score) &amp;gt;= threshold&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;similarity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1.0&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;best&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Score&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;similarity&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SimilarityThreshold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogDebug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"Cache miss for {Plugin}.{Function}: best similarity {Similarity:F3} "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
                &lt;span class="s"&gt;"below threshold {Threshold:F3}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;pluginName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;similarity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SimilarityThreshold&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// Increment hit counter asynchronously-fire and forget acceptable here&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;IncrementHitCountAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;best&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Cache hit for {Plugin}.{Function}: similarity={Similarity:F3}, "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
            &lt;span class="s"&gt;"hit_count={HitCount}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;pluginName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;similarity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;best&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HitCount&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;SemanticCacheEntry&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;ResponseText&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;best&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Similarity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;similarity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;CacheKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;best&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;SetAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;pluginName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;promptText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;responseText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;ttl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_embeddingService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GenerateEmbeddingAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;promptText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;embeddingBytes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;EmbeddingToBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cacheKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;KeyPrefix&lt;/span&gt;&lt;span class="p"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;NewGuid&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;effectiveTtl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ttl&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="n"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultTtl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;hashFields&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;HashEntry&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"plugin_name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pluginName&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"function_name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tenant_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"prompt_text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;promptText&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// Store for debugging/audit&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"response_text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;responseText&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"embedding"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;embeddingBytes&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DateTimeOffset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToUnixTimeSeconds&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hit_count"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateTransaction&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HashSetAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hashFields&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;KeyExpireAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;effectiveTtl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogDebug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Cached response for {Plugin}.{Function} with TTL={TTL}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;pluginName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;effectiveTtl&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="nf"&gt;EmbeddingToBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ReadOnlyMemory&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;embedding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
        &lt;span class="n"&gt;MemoryMarshal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AsBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;CopyTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;EscapeTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"-"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"\\-"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"\\."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt; &lt;span class="nf"&gt;DisposeAsync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CloseAsync&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;h3&gt;
  
  
  3.3 Cache Configuration Tuning
&lt;/h3&gt;

&lt;p&gt;The SimilarityThreshold parameter is the most consequential configuration value in the semantic cache. Too low and you return cached responses for queries that are semantically distinct—producing incorrect or irrelevant answers. Too high and your hit rate collapses toward zero.&lt;/p&gt;

&lt;p&gt;The recommended tuning process is empirical: deploy with threshold 0.92 as a starting point, instrument cache hit rates and user feedback signals, and adjust based on observed quality vs. hit rate trade-off. Different plugin functions warrant different thresholds—summarization is more tolerant of semantic variation than precise data extraction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SemanticCacheConfiguration&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt; &lt;span class="n"&gt;DefaultTtl&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&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;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromHours&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;MaxCandidates&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&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="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// KNN k value&lt;/span&gt;
    &lt;span class="c1"&gt;// Per-function threshold overrides&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;FunctionThresholds&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&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;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// High tolerance: question phrasing variations map to same answer&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Summarize"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.88&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AnalyzeSentiment"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

        &lt;span class="c1"&gt;// Low tolerance: subtle wording changes carry semantic weight&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"ExtractObligations"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"ExtractEntities"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.94&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"ClassifyIntent"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.93&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;DefaultThreshold&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&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="m"&gt;0.92&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nf"&gt;GetThreshold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;FunctionThresholds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TryGetValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DefaultThreshold&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;h3&gt;
  
  
  3.4 Cache Hit Rate Instrumentation
&lt;/h3&gt;

&lt;p&gt;Without measuring the cache, you cannot optimize it. Wire the cache service into your OpenTelemetry metrics pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Metrics registered at startup&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;CacheHits&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; 
    &lt;span class="n"&gt;Metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateCounter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"sk_cache_hits_total"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Total semantic cache hits"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;CacheMisses&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; 
    &lt;span class="n"&gt;Metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateCounter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"sk_cache_misses_total"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Total semantic cache misses"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Histogram&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;CacheSimilarityScore&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; 
    &lt;span class="n"&gt;Metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateHistogram&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"sk_cache_similarity_score"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Cosine similarity score of cache hits"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;CacheTokensSaved&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; 
    &lt;span class="n"&gt;Metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateCounter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"sk_cache_tokens_saved_total"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Estimated tokens saved by cache hits"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The sk_cache_hits_total / (sk_cache_hits_total + sk_cache_misses_total) ratio is your cache hit rate metric—the primary FinOps KPI for the caching layer. Target 35%+ in steady state for workloads with repetitive query patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  IV. The Complete Production Filter Pipeline
&lt;/h3&gt;

&lt;h3&gt;
  
  
  4.1 Filter Registration and Ordering
&lt;/h3&gt;

&lt;p&gt;Semantic Kernel filters execute in registration order for pre-invocation logic and in reverse order for post-invocation logic — identical to ASP.NET Core middleware ordering semantics. The order matters for correctness:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Kernel configuration with ordered filter pipeline&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Kernel&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;kernelBuilder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;kernelBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAzureOpenAIChatCompletion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;deploymentName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI:DeploymentName"&lt;/span&gt;&lt;span class="p"&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;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI:Endpoint"&lt;/span&gt;&lt;span class="p"&gt;]!,&lt;/span&gt;
        &lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI:ApiKey"&lt;/span&gt;&lt;span class="p"&gt;]!,&lt;/span&gt;
        &lt;span class="n"&gt;serviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"primary"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;kernelBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAzureOpenAIChatCompletion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;deploymentName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI:FallbackDeployment"&lt;/span&gt;&lt;span class="p"&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;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI:Endpoint"&lt;/span&gt;&lt;span class="p"&gt;]!,&lt;/span&gt;
        &lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI:ApiKey"&lt;/span&gt;&lt;span class="p"&gt;]!,&lt;/span&gt;
        &lt;span class="n"&gt;serviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"fallback"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Filter order (pre-invocation): 1 → 2 → 3 → 4 → 5&lt;/span&gt;
    &lt;span class="c1"&gt;// Filter order (post-invocation): 5 → 4 → 3 → 2 → 1&lt;/span&gt;
    &lt;span class="n"&gt;kernelBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IFunctionInvocationFilter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TenantRateLimitFilter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt; &lt;span class="c1"&gt;// 1: Gate first&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IFunctionInvocationFilter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SemanticCacheFilter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt; &lt;span class="c1"&gt;// 2: Check cache&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IFunctionInvocationFilter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PromptSanitizationFilter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt; &lt;span class="c1"&gt;// 3: Sanitize input&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IFunctionInvocationFilter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ObservabilityFilter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt; &lt;span class="c1"&gt;// 4: Measure&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IFunctionInvocationFilter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OutputValidationFilter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt; &lt;span class="c1"&gt;// 5: Validate output&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IPromptRenderFilter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AuditPromptFilter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt; &lt;span class="c1"&gt;// Audit resolved prompts&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IAutoFunctionInvocationFilter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PlannerBoundaryFilter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Bound planner&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;kernelBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&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;h3&gt;
  
  
  4.2 Tenant Rate Limit Filter
&lt;/h3&gt;

&lt;p&gt;The rate limiting filter is the outermost gate — it rejects requests before any LLM work begins, protecting both cost budgets and downstream service capacity. Implementation uses a sliding window algorithm backed by Redis atomic operations for correctness under concurrent load:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TenantRateLimitFilter&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IFunctionInvocationFilter&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ITokenBudgetService&lt;/span&gt; &lt;span class="n"&gt;_budgetService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IRateLimiterService&lt;/span&gt; &lt;span class="n"&gt;_rateLimiter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IHttpContextAccessor&lt;/span&gt; &lt;span class="n"&gt;_httpContext&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;OnFunctionInvocationAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;FunctionInvocationContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FunctionInvocationContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tenantId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ResolveTenantId&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="c1"&gt;// Check token budget before any invocation&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;budgetCheck&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_budgetService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CheckAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;budgetCheck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasBudget&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;TokenBudgetExceededException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;$"Tenant &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; has exhausted daily token budget of "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
                &lt;span class="s"&gt;$"$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;budgetCheck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DailyLimitUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;F2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
                &lt;span class="s"&gt;$"Budget resets at &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;budgetCheck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResetTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;HH&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;mm&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; UTC."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// Check request rate limit (RPM - requests per minute)&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rateCheck&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_rateLimiter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CheckRateLimitAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$"rpm:&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetRpmLimit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;rateCheck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsAllowed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;RateLimitExceededException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;$"Rate limit exceeded for tenant &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
                &lt;span class="s"&gt;$"Limit: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;rateCheck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Limit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt; RPM. "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
                &lt;span class="s"&gt;$"Retry after: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;rateCheck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RetryAfter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;F0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;s"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;RetryAfter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rateCheck&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RetryAfter&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;ResolveTenantId&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;_httpContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HttpContext&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FindFirst&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"tenant_id"&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;
        &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InvalidOperationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Tenant context not available - ensure authentication middleware runs before kernel invocation."&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;h3&gt;
  
  
  4.3 Prompt Sanitization Filter
&lt;/h3&gt;

&lt;p&gt;The prompt sanitization filter intercepts function arguments before prompt rendering, applying heuristic and model-based checks for prompt injection attempts. This operates on the IPromptRenderFilter interface, which provides access to the rendered prompt text before it is submitted to the LLM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AuditPromptFilter&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IPromptRenderFilter&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IPromptAuditStore&lt;/span&gt; &lt;span class="n"&gt;_auditStore&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IPromptInjectionDetector&lt;/span&gt; &lt;span class="n"&gt;_injectionDetector&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AuditPromptFilter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;OnPromptRenderAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;PromptRenderContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PromptRenderContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Let prompt render complete&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;renderedPrompt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RenderedPrompt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;renderedPrompt&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;// Injection detection: heuristic patterns first (cheap), LLM-based second (expensive)&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;injectionResult&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_injectionDetector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;DetectAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;renderedPrompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;injectionResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsInjectionDetected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogWarning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"Prompt injection detected for function {PluginName}.{FunctionName}. "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
                &lt;span class="s"&gt;"Confidence: {Confidence:F2}. Pattern: {Pattern}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PluginName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;injectionResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Confidence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;injectionResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DetectedPattern&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="c1"&gt;// Record security event&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_auditStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RecordSecurityEventAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;PromptSecurityEvent&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Timestamp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DateTimeOffset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;TenantId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ResolveTenantId&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;PluginName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PluginName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;FunctionName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;DetectedPattern&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;injectionResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DetectedPattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Confidence&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;injectionResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Confidence&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="c1"&gt;// Never log full prompt in audit store-may contain PII&lt;/span&gt;
                &lt;span class="n"&gt;PromptHash&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ComputeHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;renderedPrompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;injectionResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Confidence&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BlockThreshold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PromptInjectionException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;$"Prompt injection blocked with confidence &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;injectionResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Confidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;F2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&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="c1"&gt;// Audit all prompts in regulated deployments&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AuditAllPrompts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_auditStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RecordPromptAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;PromptAuditRecord&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;Timestamp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DateTimeOffset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;PluginName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PluginName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;FunctionName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;PromptHash&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ComputeHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;renderedPrompt&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;TokenEstimate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;EstimateTokenCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;renderedPrompt&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4.4 Output Validation Filter
&lt;/h3&gt;

&lt;p&gt;The output validation filter is the post-invocation gate for semantic correctness. Where infrastructure filters handle binary pass/fail scenarios, output validation handles the probabilistic quality spectrum of LLM responses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OutputValidationFilter&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IFunctionInvocationFilter&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IOutputValidatorRegistry&lt;/span&gt; &lt;span class="n"&gt;_validatorRegistry&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OutputValidationFilter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;OutputValidationConfiguration&lt;/span&gt; &lt;span class="n"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;OnFunctionInvocationAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;FunctionInvocationContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FunctionInvocationContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&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;GetValue&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;// Look up validators registered for this specific function&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;validators&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_validatorRegistry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetValidators&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PluginName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;validators&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;validationContext&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;OutputValidationContext&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;PluginName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PluginName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;FunctionName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Output&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;InputArguments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Arguments&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;validator&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;validators&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;validator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ValidateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;validationContext&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&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;IsValid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogWarning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;"Output validation failed for {Plugin}.{Function}: {Reason}. "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
                    &lt;span class="s"&gt;"Validator: {ValidatorType}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PluginName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&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;FailureReason&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;validator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetType&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="c1"&gt;// Determine escalation strategy based on validator severity&lt;/span&gt;
                &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&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;Severity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;ValidationSeverity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Critical&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="c1"&gt;// Block response entirely-cannot return this output&lt;/span&gt;
                        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OutputValidationException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                            &lt;span class="s"&gt;$"Critical output validation failure: &lt;/span&gt;&lt;span class="p"&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;FailureReason&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;ValidationSeverity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;High&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="c1"&gt;// Attempt retry with modified execution settings&lt;/span&gt;
                        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestSequenceIndex&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxRetries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                                &lt;span class="s"&gt;"Retrying {Plugin}.{Function} due to high-severity "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
                                &lt;span class="s"&gt;"validation failure (attempt {Attempt}/{Max})"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PluginName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestSequenceIndex&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="n"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxRetries&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                            &lt;span class="c1"&gt;// Signal retry-caller will re-invoke&lt;/span&gt;
                            &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&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;FunctionResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt;
                        &lt;span class="k"&gt;goto&lt;/span&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;ValidationSeverity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Medium&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;ValidationSeverity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Medium&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="c1"&gt;// Return with quality degradation signal in metadata&lt;/span&gt;
                        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;metadata&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&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;Metadata&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; 
                            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;();&lt;/span&gt;
                        &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"validation_warning"&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="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FailureReason&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                        &lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"validation_severity"&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="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Severity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;ValidationSeverity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Low&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="c1"&gt;// Log only-pass through&lt;/span&gt;
                        &lt;span class="k"&gt;break&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4.5 Planner Boundary Filter
&lt;/h3&gt;

&lt;p&gt;The planner boundary filter is the safety mechanism for auto-invoke scenarios. It enforces hard limits on plan execution — maximum steps, allowed plugin set, and cumulative token budget — preventing unbounded planning loops:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PlannerBoundaryFilter&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IAutoFunctionInvocationFilter&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;PlannerBoundaryConfiguration&lt;/span&gt; &lt;span class="n"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PlannerBoundaryFilter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;OnAutoFunctionInvocationAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;AutoFunctionInvocationContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AutoFunctionInvocationContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Enforce maximum execution steps&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestSequenceIndex&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxPlannerSteps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogWarning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"Planner exceeded maximum step count of {MaxSteps} for goal: {Goal}. "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
                &lt;span class="s"&gt;"Terminating plan execution."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxPlannerSteps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChatHistory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LastOrDefault&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;span class="nf"&gt;Truncate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
            &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Terminate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// Enforce plugin allowlist&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;requestedPlugin&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PluginName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AllowedPlugins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requestedPlugin&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogWarning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"Planner attempted to invoke unauthorized plugin {PluginName}. "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
                &lt;span class="s"&gt;"Blocked by boundary filter."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;requestedPlugin&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="c1"&gt;// Don't throw-instead provide a synthetic result explaining the restriction&lt;/span&gt;
            &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FunctionResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s"&gt;$"Plugin '&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;requestedPlugin&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;' is not authorized for autonomous invocation."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="c1"&gt;// Post-step: check cumulative token consumption&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;stepUsage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&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;Metadata&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetValueOrDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Usage"&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;CompletionsUsage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stepUsage&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cumulativeTokens&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;TrackCumulativeTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stepUsage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cumulativeTokens&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxPlannerTokenBudget&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogWarning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;"Planner exceeded token budget of {Budget} tokens "&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt;
                    &lt;span class="s"&gt;"(consumed: {Consumed}). Terminating plan."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxPlannerTokenBudget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;cumulativeTokens&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Terminate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  V. End-to-End Wiring: The Complete Kernel Bootstrap
&lt;/h3&gt;

&lt;p&gt;With all components defined, the production bootstrap assembles them into a coherent, observable, resilient system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Program.cs — Production Semantic Kernel Bootstrap&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateApplicationBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// ── Infrastructure ──────────────────────────────────────────────────────────&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddStackExchangeRedisCache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Redis:ConnectionString"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InstanceName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"sk-prod:"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IConnectionMultiplexer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ConnectionMultiplexer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Redis:ConnectionString"&lt;/span&gt;&lt;span class="p"&gt;]!));&lt;/span&gt;
&lt;span class="c1"&gt;// ── Semantic Cache ──────────────────────────────────────────────────────────&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SemanticCacheConfiguration&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetSection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SemanticCache"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SemanticCacheConfiguration&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()!);&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ISemanticCacheService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RedisSemanticCacheService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddHostedService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SemanticCacheIndexInitializer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Ensures index on startup&lt;/span&gt;
&lt;span class="c1"&gt;// ── Resilience ──────────────────────────────────────────────────────────────&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddHttpClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI-Primary"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddResilienceHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"llm-primary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;ConfigureLlmResiliencePipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isPrimary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddHttpClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI-Fallback"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddResilienceHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"llm-fallback"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;ConfigureLlmResiliencePipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isPrimary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// ── Kernel ──────────────────────────────────────────────────────────────────&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Kernel&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;kernelBuilder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// Models&lt;/span&gt;
    &lt;span class="n"&gt;kernelBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAzureOpenAIChatCompletion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;deploymentName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI:GPT4oDeployment"&lt;/span&gt;&lt;span class="p"&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;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI:Endpoint"&lt;/span&gt;&lt;span class="p"&gt;]!,&lt;/span&gt;
        &lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI:ApiKey"&lt;/span&gt;&lt;span class="p"&gt;]!,&lt;/span&gt;
        &lt;span class="n"&gt;serviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"primary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;httpClient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IHttpClientFactory&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI-Primary"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;kernelBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAzureOpenAIChatCompletion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;deploymentName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI:GPT4oMiniDeployment"&lt;/span&gt;&lt;span class="p"&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;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI:Endpoint"&lt;/span&gt;&lt;span class="p"&gt;]!,&lt;/span&gt;
        &lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI:ApiKey"&lt;/span&gt;&lt;span class="p"&gt;]!,&lt;/span&gt;
        &lt;span class="n"&gt;serviceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"fallback"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;httpClient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IHttpClientFactory&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI-Fallback"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;kernelBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAzureOpenAITextEmbeddingGeneration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;deploymentName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI:EmbeddingDeployment"&lt;/span&gt;&lt;span class="p"&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;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI:Endpoint"&lt;/span&gt;&lt;span class="p"&gt;]!,&lt;/span&gt;
        &lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI:ApiKey"&lt;/span&gt;&lt;span class="p"&gt;]!);&lt;/span&gt;
    &lt;span class="c1"&gt;// Plugins&lt;/span&gt;
    &lt;span class="n"&gt;kernelBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Plugins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddFromType&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DocumentAnalysisPlugin&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"DocumentPlugin"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;kernelBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Plugins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddFromType&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;LegalAnalysisPlugin&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"LegalPlugin"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;kernelBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Plugins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddFromType&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;NotificationPlugin&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"NotificationPlugin"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Filter Pipeline (order is significant)&lt;/span&gt;
    &lt;span class="n"&gt;kernelBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IFunctionInvocationFilter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
            &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TenantRateLimitFilter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IFunctionInvocationFilter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
            &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SemanticCacheFilter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IFunctionInvocationFilter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
            &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ObservabilityFilter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IFunctionInvocationFilter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
            &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OutputValidationFilter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IPromptRenderFilter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
            &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AuditPromptFilter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IAutoFunctionInvocationFilter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
            &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PlannerBoundaryFilter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;());&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;kernelBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// ── OpenTelemetry ───────────────────────────────────────────────────────────&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOpenTelemetry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ConfigureResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SemanticKernelService"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;serviceVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"2.0.0"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithTracing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAspNetCoreInstrumentation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddHttpClientInstrumentation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.SemanticKernel*"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SemanticKernel.Custom*"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOtlpExporter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Endpoint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; 
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"OpenTelemetry:Endpoint"&lt;/span&gt;&lt;span class="p"&gt;]!)))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithMetrics&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAspNetCoreInstrumentation&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMeter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Microsoft.SemanticKernel*"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMeter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"SemanticKernel.Custom"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddOtlpExporter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Endpoint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; 
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"OpenTelemetry:Endpoint"&lt;/span&gt;&lt;span class="p"&gt;]!)));&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;RunAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  VI. Operational Runbook: What to Watch in Production
&lt;/h3&gt;

&lt;h3&gt;
  
  
  6.1 The Five Metrics That Matter Most
&lt;/h3&gt;

&lt;p&gt;Once the system is running, these five metrics define your production health posture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;| Metric | Alert Threshold | Action on Breach |
|-------------------------------------|------------------------|----------------------------------------|
| sk_cache_hit_rate (7d avg) | &amp;lt; 25% | Review query diversity, adjust TTL |
| sk_function_duration_p99 | &amp;gt; 12s | Check model latency, circuit breakers |
| sk_estimated_cost_usd_total (daily) | &amp;gt; 110% of daily budget | Activate model tiering, alert FinOps |
| sk_circuit_breaker_opened_total | Any increment | Page on-call, activate fallback |
| sk_output_validation_failures_total | &amp;gt; 2% of invocations | Review prompt quality, check for drift |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6.2 Incident Response Patterns
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom: Sudden latency spike (P99 &amp;gt; 15s)&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check circuit breaker state — if open, primary model is degraded&lt;/li&gt;
&lt;li&gt;Verify fallback model is receiving traffic and responding within SLA&lt;/li&gt;
&lt;li&gt;Check Redis cache hit rate — a spike in misses increases LLM load&lt;/li&gt;
&lt;li&gt;Review FT.INFO semantic-cache-idx for index health&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Symptom: Cost burn rate 2× normal&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check cache hit rate — a cache index rebuild or Redis failover may have cleared cached entries&lt;/li&gt;
&lt;li&gt;Review planner step counts — unbounded planning may have slipped through boundary filter&lt;/li&gt;
&lt;li&gt;Audit token consumption by plugin — identify highest-cost functions&lt;/li&gt;
&lt;li&gt;Check for prompt injection events — attackers deliberately inflating token usage&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Symptom: Elevated output validation failures&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pull sample failed outputs from audit store&lt;/li&gt;
&lt;li&gt;Check if a prompt template was recently updated — prompt regressions are the primary cause&lt;/li&gt;
&lt;li&gt;Verify LLM model version hasn’t changed in Azure OpenAI deployment&lt;/li&gt;
&lt;li&gt;Review input distribution — a change in user query patterns may be exposing prompt brittleness&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  VII. Key Takeaways and What’s Next
&lt;/h3&gt;

&lt;p&gt;Part 2 has walked through the three implementation layers that bridge architectural intent and production reality.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;async parallel orchestration&lt;/strong&gt; pattern transforms multi-step LLM workflows from sequential 6–10 second operations into concurrent 2–3 second operations — not through faster LLMs, but through correct use of Task composition and dependency-aware DAG execution.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Redis semantic cache&lt;/strong&gt; eliminates token costs for repeated semantic intent — the most impactful FinOps lever available at this layer of the stack. The implementation details that matter most are KNN vector indexing with HNSW, per-function similarity thresholds, and TTL policies that balance freshness against hit rate.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;complete filter pipeline&lt;/strong&gt; is what separates a Semantic Kernel proof-of-concept from a system an enterprise can operate. Rate limiting, prompt sanitization, token metering, output validation, and planner boundary enforcement are not optional hardening steps — they are the production system.&lt;/p&gt;

&lt;p&gt;In Part 3, we will move into advanced territory: implementing a multi-agent orchestration architecture where specialized kernel instances collaborate on complex tasks, building a domain-specific memory system for regulated industries with PII redaction and audit-trail requirements, and exploring the emerging Semantic Kernel Process Framework for stateful, long-running AI workflows that survive service restarts and scale across distributed nodes.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article is Part 2 of a series on Semantic Kernel for Enterprise AI in .NET.&lt;/em&gt; &lt;a href="https://dev.to/topuzas/semantic-kernel-for-enterprise-ai-architecting-production-grade-llm-integration-in-net-13nm-temp-slug-7563030"&gt;&lt;em&gt;Part 1&lt;/em&gt;&lt;/a&gt; &lt;em&gt;covered foundational architecture, FinOps cost modeling, and SRE reliability patterns.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>distributedsystems</category>
      <category>semantickernel</category>
      <category>sre</category>
    </item>
    <item>
      <title>Semantic Kernel for Enterprise AI: Architecting Production-Grade LLM Integration in .NET</title>
      <dc:creator>Ali Suleyman TOPUZ</dc:creator>
      <pubDate>Sat, 21 Mar 2026 18:47:34 +0000</pubDate>
      <link>https://forem.com/topuzas/semantic-kernel-for-enterprise-ai-architecting-production-grade-llm-integration-in-net-384j</link>
      <guid>https://forem.com/topuzas/semantic-kernel-for-enterprise-ai-architecting-production-grade-llm-integration-in-net-384j</guid>
      <description>&lt;h3&gt;
  
  
  Semantic Kernel for Enterprise AI: Architecting Production-Grade LLM Integration in .NET — Foundations — Part 1
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;This article is Part 1 of a series on Semantic Kernel for Enterprise AI in .NET. I work at the intersection of distributed systems, AI infrastructure, and .NET engineering.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  I. Executive Summary
&lt;/h3&gt;

&lt;h3&gt;
  
  
  The Gap Between Demo and Production
&lt;/h3&gt;

&lt;p&gt;Every engineering team that has delivered an LLM proof-of-concept eventually faces the same humbling reality: a polished ChatGPT-style demo is light years away from a production system that handles real business transactions under real load, with real money on the line. The raw OpenAI or Azure OpenAI API gets you to the demo in days. Getting from there to a system that a Fortune 500 organization can stake its operations on — that takes an architectural framework purpose-built for the challenge.&lt;/p&gt;

&lt;p&gt;Microsoft’s &lt;strong&gt;Semantic Kernel&lt;/strong&gt; is that framework for .NET engineers.&lt;/p&gt;

&lt;p&gt;At its core, Semantic Kernel is an open-source SDK that functions as an orchestration layer — a sophisticated middleware bridging the deterministic world of traditional enterprise software with the probabilistic, latency-sensitive, and expensive world of Large Language Models. It is not a thin API wrapper. It is not a prompt-templating library. It is a full orchestration runtime with first-class support for skills composition, autonomous planning, semantic memory, token optimization, circuit breakers, and OpenTelemetry-grade observability. Every one of those capabilities was forged in the furnace of Microsoft’s own internal AI deployments — Copilot across Office, GitHub Copilot, Azure AI services — serving hundreds of millions of users where failures are measured in dollars, not just SLA percentages.&lt;/p&gt;

&lt;p&gt;For senior engineers operating at the intersection of AI, distributed systems, and .NET, this article is a deep architectural walkthrough of what Semantic Kernel is, why it exists in its current form, and how to wield it at the level of a production systems architect rather than a tutorial consumer.&lt;/p&gt;

&lt;h3&gt;
  
  
  II. Why Raw LLM APIs Are Necessary but Insufficient
&lt;/h3&gt;

&lt;p&gt;Before examining Semantic Kernel’s architecture, we need to name the production failure modes that motivate its existence. These are problems that emerge reliably once an LLM-powered system moves beyond controlled demos.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.1 Prompt Management at Scale
&lt;/h3&gt;

&lt;p&gt;In a demo, your prompt is a string in your source code. In production, prompts are configuration artifacts that must be versioned, deployed independently of code, A/B tested, localized, role-conditioned, and audited. A single enterprise AI agent may require dozens of prompt templates, each evolving independently. Managing this through string interpolation in C# is an operational disaster waiting to happen.&lt;/p&gt;

&lt;p&gt;Semantic Kernel introduces a structured prompt template system — YAML-based prompt configuration with variable injection, function calling declarations, and execution settings — that treats prompts as first-class, versionable assets rather than embedded strings.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.2 Context Window Economics
&lt;/h3&gt;

&lt;p&gt;GPT-4’s context window is large but not infinite, and every token you send costs money. In a naive implementation, developers stuff entire conversation histories into every API call, leading to two correlated failure modes: context window overflow when histories grow long, and exponentially escalating costs as conversation depth increases. The economic model of LLMs means that architectural inefficiency translates directly and immediately to the FinOps P&amp;amp;L.&lt;/p&gt;

&lt;p&gt;Semantic Kernel’s memory abstractions — backed by vector stores like Azure AI Search, Qdrant, or Chroma — enable selective context retrieval through semantic similarity rather than brute-force history injection. This is not a convenience feature; it is a cost-control mechanism that can reduce per-conversation token spend by an order of magnitude at enterprise scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.3 Non-Deterministic Failure Modes
&lt;/h3&gt;

&lt;p&gt;Traditional enterprise systems fail in ways that ops teams have learned to handle: service unavailable (503), timeout, null reference, constraint violation. These are binary, deterministic, and observable. LLM failures are none of these things. An LLM can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Return a response that is syntactically valid JSON but semantically incorrect&lt;/li&gt;
&lt;li&gt;Confidently hallucinate facts, API parameters, or code behavior&lt;/li&gt;
&lt;li&gt;Produce outputs that drift in quality as prompt context fills up&lt;/li&gt;
&lt;li&gt;Fail silently when a function call argument is subtly malformed&lt;/li&gt;
&lt;li&gt;Exhibit prompt injection vulnerabilities when user input is insufficiently sandboxed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your circuit breaker doesn’t catch hallucinations. Your retry policy doesn’t fix semantic drift. Addressing this class of failure demands architectural patterns — output validation pipelines, structured output enforcement, semantic guardrails, and human-in-the-loop escalation hooks — that raw API calls cannot provide.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.4 Multi-Model, Multi-Provider Orchestration
&lt;/h3&gt;

&lt;p&gt;No enterprise runs a single LLM for all use cases. GPT-4o handles complex reasoning; GPT-3.5-turbo handles high-volume simple classification at a fraction of the cost; a fine-tuned domain model handles specialized extraction. Routing intelligently across these models — based on task complexity, cost budget, latency SLA, and availability — requires an abstraction layer that decouples your business logic from any specific model’s API surface.&lt;/p&gt;

&lt;p&gt;Semantic Kernel’s connector architecture provides exactly this. You register multiple model services with service IDs, define routing strategies, and let the orchestration layer handle model selection. Your skills and planners operate against the abstraction, not the concrete API.&lt;/p&gt;

&lt;h3&gt;
  
  
  III. Architectural Anatomy of Semantic Kernel
&lt;/h3&gt;

&lt;p&gt;Understanding Semantic Kernel at an architectural level requires examining its four foundational concepts: &lt;strong&gt;Plugins (Skills)&lt;/strong&gt;, &lt;strong&gt;Planners&lt;/strong&gt; , &lt;strong&gt;Memory&lt;/strong&gt; , and &lt;strong&gt;Filters (Middleware)&lt;/strong&gt;. These are not independent features — they compose into an orchestration runtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.1 Plugins: The Unit of AI Capability
&lt;/h3&gt;

&lt;p&gt;A plugin in Semantic Kernel is a class that exposes one or more functions — either semantic functions (backed by LLM invocations) or native functions (backed by deterministic C# logic) — that the kernel can discover, invoke, and compose. The [KernelFunction] attribute and [Description] annotations are not decorative; they are the mechanism by which the planner understands what a function does and when to invoke it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DocumentAnalysisPlugin&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Kernel&lt;/span&gt; &lt;span class="n"&gt;_kernel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IDocumentStore&lt;/span&gt; &lt;span class="n"&gt;_documentStore&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;DocumentAnalysisPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Kernel&lt;/span&gt; &lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IDocumentStore&lt;/span&gt; &lt;span class="n"&gt;documentStore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_kernel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_documentStore&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;documentStore&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;KernelFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"summarize_document"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Produces a structured executive summary of a business document given its ID"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DocumentSummary&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;SummarizeDocumentAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"The unique identifier of the document to summarize"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;documentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Target audience: executive, technical, or legal"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;audience&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"executive"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;document&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_documentStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;documentId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;arguments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;KernelArguments&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"document_content"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;document&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;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"audience"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;audience&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"max_length"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;audience&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"executive"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s"&gt;"200"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"500"&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InvokeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"DocumentPrompts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
            &lt;span class="s"&gt;"SummarizeForAudience"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
            &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;JsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Deserialize&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DocumentSummary&lt;/span&gt;&lt;span class="p"&gt;&amp;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;GetValue&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;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;KernelFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"extract_obligations"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Extracts legal obligations, deadlines, and parties from contract documents"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ContractObligations&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;ExtractObligationsAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Raw contract text to analyze"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;contractText&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Native validation before LLM invocation-keep deterministic logic out of prompts&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contractText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;100_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Contract text exceeds maximum analyzable length"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;arguments&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;KernelArguments&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"contract_text"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contractText&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InvokeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"LegalPrompts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
            &lt;span class="s"&gt;"ExtractObligations"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
            &lt;span class="n"&gt;arguments&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;ParseObligations&lt;/span&gt;&lt;span class="p"&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;GetValue&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;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 architectural principle here is the &lt;strong&gt;separation of semantic and native logic&lt;/strong&gt;. Input validation, data access, and output parsing belong in native functions. Reasoning, synthesis, and language understanding belong in semantic functions. Mixing these concerns produces systems that are harder to test, harder to debug, and more expensive to run.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.2 Planners: Autonomous Orchestration
&lt;/h3&gt;

&lt;p&gt;The planner is Semantic Kernel’s most architecturally significant component — and the one most likely to surprise engineers coming from traditional orchestration systems like Temporal or Azure Durable Functions. A planner takes a high-level goal expressed in natural language and dynamically generates a plan — a sequence of plugin invocations — to achieve it, using the LLM itself as the planning engine.&lt;/p&gt;

&lt;p&gt;Semantic Kernel currently supports two primary planner strategies:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Handlebars Planner&lt;/strong&gt; generates a structured Handlebars template representing a fixed execution plan. This is deterministic once generated — suitable for workflows where you want human review of the plan before execution, or where the execution graph needs to be auditable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Function Calling Planner (Auto-invoke)&lt;/strong&gt; delegates step-by-step planning to the LLM’s native function-calling capability, allowing iterative, dynamic execution where the model decides at each step which function to invoke based on prior results. This is appropriate for exploratory or open-ended tasks.&lt;/p&gt;

&lt;p&gt;For production enterprise systems, the architectural guidance is this: &lt;strong&gt;never use open-ended auto-invoke without bounding constraints&lt;/strong&gt;. Unbounded planners can generate execution graphs of arbitrary length, incurring unbounded token costs and latency. Always configure FunctionChoiceBehavior with an explicit maximum step count and a function allowlist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;executionSettings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;OpenAIPromptExecutionSettings&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;FunctionChoiceBehavior&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FunctionChoiceBehavior&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Auto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;functions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;allowedFunctions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Constrain available functions&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;FunctionChoiceBehaviorOptions&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;AllowConcurrentInvocation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Parallel execution where safe&lt;/span&gt;
            &lt;span class="n"&gt;AllowParallelCalls&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&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;MaxTokens&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;2048&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Temperature&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.1&lt;/span&gt; &lt;span class="c1"&gt;// Low temperature for planning tasks—higher determinism&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3.3 Memory: Semantic Context Management
&lt;/h3&gt;

&lt;p&gt;Semantic Kernel’s memory system abstracts vector storage and retrieval behind a clean interface that integrates naturally with the kernel’s execution pipeline. The architectural pattern here is &lt;strong&gt;Retrieval-Augmented Generation (RAG)&lt;/strong&gt;, but implemented at the framework level rather than hand-rolled per application.&lt;/p&gt;

&lt;p&gt;The key design decision is between &lt;strong&gt;volatile memory&lt;/strong&gt; (in-process, session-scoped) and &lt;strong&gt;persistent memory&lt;/strong&gt; (vector database-backed, cross-session). In enterprise applications, persistent memory backed by Azure AI Search or Qdrant is the standard pattern — it enables knowledge bases that grow over time, cross-session context, and shared memory across multiple kernel instances in a horizontally scaled deployment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Memory configuration with Azure AI Search&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;memoryBuilder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MemoryBuilder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;memoryBuilder&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithAzureOpenAITextEmbeddingGeneration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;deploymentName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI:EmbeddingDeployment"&lt;/span&gt;&lt;span class="p"&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;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI:Endpoint"&lt;/span&gt;&lt;span class="p"&gt;]!,&lt;/span&gt;
        &lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI:ApiKey"&lt;/span&gt;&lt;span class="p"&gt;]!)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithMemoryStore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AzureAISearchMemoryStore&lt;/span&gt;&lt;span class="p"&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;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AzureSearch:Endpoint"&lt;/span&gt;&lt;span class="p"&gt;]!,&lt;/span&gt;
        &lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AzureSearch:ApiKey"&lt;/span&gt;&lt;span class="p"&gt;]!));&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;memoryBuilder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// Storing knowledge&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveInformationAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"product-knowledge-base"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;productDocument&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;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;productDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;productDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Semantic retrieval-returns chunks ranked by cosine similarity&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SearchAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"product-knowledge-base"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;userQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;minRelevanceScore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToListAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The minRelevanceScore parameter is operationally critical. In production systems, you need a floor on retrieval quality to prevent low-relevance noise from polluting the LLM's context window—which degrades response quality while increasing token costs simultaneously.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.4 Filters: The Middleware Pipeline
&lt;/h3&gt;

&lt;p&gt;Semantic Kernel’s filter pipeline is its most underutilized and most architecturally powerful feature for enterprise hardening. Filters are middleware components that intercept function invocations — both before and after execution — enabling cross-cutting concerns to be implemented once and applied uniformly across all kernel operations.&lt;/p&gt;

&lt;p&gt;The three filter interfaces are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IFunctionInvocationFilter — intercepts individual plugin function calls&lt;/li&gt;
&lt;li&gt;IPromptRenderFilter — intercepts prompt construction before LLM submission&lt;/li&gt;
&lt;li&gt;IAutoFunctionInvocationFilter — intercepts auto-planner function calls specifically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For enterprise systems, the canonical set of filters to implement includes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rate Limiting Filter&lt;/strong&gt;  — enforces per-user, per-tenant, or global token budget constraints, rejecting invocations that would exceed configured limits before they hit the LLM API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Semantic Cache Filter&lt;/strong&gt;  — checks a Redis-backed semantic similarity cache before forwarding requests to the LLM, returning cached responses for semantically equivalent queries. This is the single highest-ROI FinOps optimization available in the stack.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audit Filter&lt;/strong&gt;  — writes a structured audit log of every LLM invocation including the resolved prompt, model parameters, token consumption, and response — essential for regulated industries where AI decision auditing is a compliance requirement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Output Validation Filter&lt;/strong&gt;  — applies post-invocation validation rules (schema validation, content policy checks, confidence scoring) and triggers retry or escalation logic when validation fails.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SemanticCacheFilter&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IFunctionInvocationFilter&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ISemanticCache&lt;/span&gt; &lt;span class="n"&gt;_cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SemanticCacheFilter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;CacheConfiguration&lt;/span&gt; &lt;span class="n"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;OnFunctionInvocationAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;FunctionInvocationContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FunctionInvocationContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Only cache semantic function invocations, not native functions&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsSemanticFunction&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cacheKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;BuildCacheKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"Cache hit for function {FunctionName}. Tokens saved: ~{EstimatedTokens}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nf"&gt;EstimateTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

            &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FunctionResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Skip LLM invocation entirely&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// Cache miss-execute and store&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&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;context&lt;/span&gt;&lt;span class="p"&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;GetValue&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&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;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TtlMinutes&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;private&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;BuildCacheKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FunctionInvocationContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Cache key must capture the semantic intent, not just surface syntax&lt;/span&gt;
        &lt;span class="c1"&gt;// Use embedding similarity rather than string equality for cache lookup&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Arguments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PluginName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;ComputeSemanticHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  IV. FinOps Architecture: Treating Token Costs as Infrastructure
&lt;/h3&gt;

&lt;p&gt;Token costs are not an afterthought. At enterprise scale, they are a primary cost driver that must be engineered with the same rigor as compute and storage costs. This section outlines the architectural patterns that constitute a mature FinOps posture for Semantic Kernel deployments.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.1 The Token Cost Model
&lt;/h3&gt;

&lt;p&gt;Understanding the cost model is prerequisite to optimizing it. Azure OpenAI pricing operates on a per-million-token basis, differentiated between input (prompt) tokens and output (completion) tokens, with output tokens typically priced at a 3× multiplier. For GPT-4o as of this writing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input: ~$2.50 per 1M tokens&lt;/li&gt;
&lt;li&gt;Output: ~$10.00 per 1M tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A production AI agent handling 10,000 conversations per day, each averaging 8,000 input tokens and 500 output tokens, incurs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Daily input cost: 10,000 × 8,000 / 1,000,000 × $2.50 =  &lt;strong&gt;$200/day&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Daily output cost: 10,000 × 500 / 1,000,000 × $10.00 =  &lt;strong&gt;$50/day&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Monthly total: &lt;strong&gt;~$7,500/month&lt;/strong&gt; from token costs alone&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The leverage points for FinOps optimization, in approximate order of impact:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Semantic caching&lt;/strong&gt;  — Returning cached responses for semantically equivalent queries eliminates token costs entirely for cache hits. In enterprise applications with repetitive query patterns (FAQ-style, report generation, classification), cache hit rates of 30–60% are achievable, representing proportional cost reductions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Model tiering&lt;/strong&gt;  — Routing simple tasks (intent classification, entity extraction from structured text, short summarization) to GPT-3.5-turbo or GPT-4o-mini rather than GPT-4o can reduce per-invocation costs by 10–50× for those tasks. The routing logic belongs in a Semantic Kernel filter or a custom ITextGenerationService implementation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Prompt compression&lt;/strong&gt;  — Systematically auditing and reducing prompt verbosity. Every word in your system prompt costs money on every invocation. Compressing a system prompt from 800 tokens to 400 tokens halves input costs for that plugin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Context window management&lt;/strong&gt;  — Implementing sliding window context truncation rather than naive full-history injection. Keeping the last N turns plus retrieved semantic memory rather than the complete conversation history.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Streaming with early termination&lt;/strong&gt;  — Using GetStreamingChatMessageContentsAsync() and terminating the stream once enough content has been generated to satisfy the use case, avoiding payment for completion tokens you don't need.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.2 Token Budget Enforcement Architecture
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TokenBudgetService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ITokenBudgetService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IDistributedCache&lt;/span&gt; &lt;span class="n"&gt;_cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;TokenBudgetConfiguration&lt;/span&gt; &lt;span class="n"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;BudgetCheckResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;CheckAndDeductAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;estimatedInputTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;estimatedOutputTokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;budgetKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$"token-budget:&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;yyyy&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;MM&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;dd&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;currentUsage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;GetUsageAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;budgetKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;projectedCost&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;CalculateCost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;currentUsage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InputTokens&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;estimatedInputTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;currentUsage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OutputTokens&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;estimatedOutputTokens&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;projectedCost&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DailyBudgetUsd&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;BudgetCheckResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exceeded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;currentUsage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;currentUsage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalCostUsd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DailyBudgetUsd&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="n"&gt;resetTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddDays&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// Optimistic deduction-reconcile against actual usage post-invocation&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;IncrementUsageAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;budgetKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;estimatedInputTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;estimatedOutputTokens&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;BudgetCheckResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Approved&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  V. SRE Reliability Patterns for Semantic Kernel
&lt;/h3&gt;

&lt;p&gt;Production AI systems must meet the same availability and reliability standards as any other mission-critical service. The SRE challenge with LLM-backed systems is that the failure domain is richer and less familiar than traditional backend services.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1 The AI System Failure Taxonomy
&lt;/h3&gt;

&lt;p&gt;Before designing for reliability, enumerate what you’re designing against:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;| Failure Class | Description | Detection Signal | Mitigation Pattern |
|-------------------------|------------------------------------------|-----------------------------------|---------------------------------------------|
| Model Unavailability | LLM API returns 503/429 | HTTP status codes | Circuit breaker + fallback model |
| Rate Limit Exhaustion | TPM/RPM limits exceeded | 429 with Retry-After header | Exponential backoff + quota management |
| Context Overflow | Input exceeds context window | 400 with context length error | Dynamic context truncation |
| Semantic Degradation | Output quality falls below threshold | Custom quality scorer | Output validation + retry |
| Prompt Injection | Malicious user input hijacks prompt | Heuristic + LLM-as-judge | Input sanitization filter |
| Hallucination | Factually incorrect output | Grounding verification | RAG + citation enforcement |
| Token Budget Exhaustion | Daily spend limit reached | Budget service check | Graceful degradation to cached/static |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5.2 Circuit Breaker Implementation
&lt;/h3&gt;

&lt;p&gt;The Polly library integrates naturally with Semantic Kernel’s HttpClient pipeline, providing circuit breaker, retry, and bulkhead patterns that protect against cascading failures from LLM API instability.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Configure resilience pipeline for LLM HTTP clients&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddHttpClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddResilienceHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"llm-pipeline"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pipeline&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Retry with exponential backoff—respects Retry-After headers from 429 responses&lt;/span&gt;
        &lt;span class="n"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;HttpRetryStrategyOptions&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;MaxRetryAttempts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Delay&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;BackoffType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DelayBackoffType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exponential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;UseJitter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ShouldHandle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Outcome&lt;/span&gt; &lt;span class="k"&gt;switch&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="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HttpStatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TooManyRequests&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;PredicateResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;Result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HttpStatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServiceUnavailable&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;PredicateResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HttpRequestException&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;PredicateResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;True&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;PredicateResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;OnRetry&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Extract Retry-After header and honor it&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Outcome&lt;/span&gt;&lt;span class="p"&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;Headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RetryAfter&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;Delta&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt; &lt;span class="n"&gt;retryAfter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Arguments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ResiliencePropertyKey&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="s"&gt;"retry-after"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
                        &lt;span class="n"&gt;retryAfter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletedTask&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="c1"&gt;// Circuit breaker-trips after 5 consecutive failures, resets after 30 seconds&lt;/span&gt;
        &lt;span class="n"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddCircuitBreaker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;HttpCircuitBreakerStrategyOptions&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;SamplingDuration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;MinimumThroughput&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;FailureRatio&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;BreakDuration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;OnOpened&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Alert SRE team and activate fallback routing&lt;/span&gt;
                &lt;span class="n"&gt;MetricsRegistry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CircuitBreakerOpened&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;TagList&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"primary-gpt4"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletedTask&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="c1"&gt;// Timeout-LLM calls should complete within SLA&lt;/span&gt;
        &lt;span class="n"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;30&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;h3&gt;
  
  
  5.3 Multi-Model Failover Strategy
&lt;/h3&gt;

&lt;p&gt;When the primary model’s circuit breaker is open, the system must degrade gracefully rather than fail completely. The architectural pattern is a fallback chain: primary GPT-4o → fallback GPT-3.5-turbo → cached response → static degraded response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ResilientKernelService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IResilientKernelService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Kernel&lt;/span&gt; &lt;span class="n"&gt;_primaryKernel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// GPT-4o&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Kernel&lt;/span&gt; &lt;span class="n"&gt;_fallbackKernel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// GPT-3.5-turbo&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ISemanticCache&lt;/span&gt; &lt;span class="n"&gt;_cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ICircuitBreakerRegistry&lt;/span&gt; &lt;span class="n"&gt;_circuitBreakers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;InvokeWithFallbackAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;pluginName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;KernelArguments&lt;/span&gt; &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Check circuit breaker state before attempting primary&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;_circuitBreakers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsOpen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"primary-gpt4"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_primaryKernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InvokeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;pluginName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;return&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;GetValue&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()!;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;IsTransientFailure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;_circuitBreakers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RecordFailure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"primary-gpt4"&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="c1"&gt;// Fallback to cheaper/more available model&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_fallbackKernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InvokeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;pluginName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="n"&gt;MetricsRegistry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FallbackInvocations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;TagList&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"primary-unavailable"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

            &lt;span class="k"&gt;return&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;GetValue&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()!;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Last resort: return cached response if available&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cacheKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;BuildCacheKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pluginName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;MetricsRegistry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FallbackInvocations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;TagList&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"all-models-unavailable-cache-hit"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="c1"&gt;// Propagate only if no fallback is available&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AiServiceUnavailableException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"All AI service tiers are unavailable and no cached response exists."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  VI. Observability: The Foundation of Production AI Operations
&lt;/h3&gt;

&lt;p&gt;You cannot operate what you cannot observe. This principle, axiomatic in traditional SRE, becomes even more critical in AI systems where failures are probabilistic and often invisible at the infrastructure layer. An LLM returning a hallucinated response looks exactly like a successful 200 OK from the network’s perspective.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.1 The Three-Layer Observability Model
&lt;/h3&gt;

&lt;p&gt;Enterprise Semantic Kernel observability operates at three distinct layers, each requiring different instrumentation:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure Layer&lt;/strong&gt;  — Traditional metrics: request rates, error rates, latency percentiles, HTTP status codes. Semantic Kernel emits OpenTelemetry traces and metrics natively from Microsoft.SemanticKernel.* activity sources. Wire these into your existing observability stack (Azure Monitor, Datadog, Prometheus/Grafana) with zero custom code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resource Consumption Layer&lt;/strong&gt;  — Token-level metrics: input token count, output token count, model invoked, cost estimate. These require custom instrumentation via a IFunctionInvocationFilter that reads token usage from the FunctionInvocationContext.Result.Metadata.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Semantic Quality Layer&lt;/strong&gt;  — The layer most organizations skip and later regret. Metrics around output quality: confidence scores, grounding percentages, retrieval relevance scores, user satisfaction signals. These require purpose-built evaluation infrastructure, typically implemented as a background evaluation pipeline that samples production outputs and scores them against quality criteria.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ObservabilityFilter&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IFunctionInvocationFilter&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Histogram&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;InvocationDuration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; 
        &lt;span class="n"&gt;Metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateHistogram&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"sk_function_duration_seconds"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"Duration of Semantic Kernel function invocations"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;InstrumentDescription&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Unit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"s"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TokensConsumed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; 
        &lt;span class="n"&gt;Metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateCounter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"sk_tokens_consumed_total"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"Total tokens consumed by Semantic Kernel functions"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;EstimatedCostUsd&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; 
        &lt;span class="n"&gt;Metrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateCounter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"sk_estimated_cost_usd_total"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"Estimated cost in USD of Semantic Kernel function invocations"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;OnFunctionInvocationAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;FunctionInvocationContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FunctionInvocationContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;stopwatch&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Stopwatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartNew&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;activity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ActivitySource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartActivity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;$"sk.function.&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PluginName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ActivityKind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Internal&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;SetTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sk.plugin.name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PluginName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;SetTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sk.function.name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="c1"&gt;// Capture token usage from response metadata&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;usage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&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;Metadata&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetValueOrDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Usage"&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;CompletionsUsage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;TagList&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"plugin"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PluginName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&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;Metadata&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
                        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetValueOrDefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ModelId"&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;"unknown"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"token_type"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"input"&lt;/span&gt;
                &lt;span class="p"&gt;};&lt;/span&gt;
                &lt;span class="n"&gt;TokensConsumed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PromptTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"token_type"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"output"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="n"&gt;TokensConsumed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletionTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cost&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;CalculateCost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PromptTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                    &lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletionTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;()!);&lt;/span&gt;
                &lt;span class="n"&gt;EstimatedCostUsd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;TagList&lt;/span&gt; 
                &lt;span class="p"&gt;{&lt;/span&gt; 
                    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"plugin"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PluginName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; 
                &lt;span class="p"&gt;});&lt;/span&gt;
                &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;SetTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sk.tokens.prompt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PromptTokens&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;SetTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sk.tokens.completion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletionTokens&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;SetTag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sk.cost.usd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cost&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;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;exception&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;activity&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;SetStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ActivityStatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&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="k"&gt;throw&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;finally&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;stopwatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;resultTags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;TagList&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"plugin"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PluginName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"function"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;exception&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;
            &lt;span class="n"&gt;InvocationDuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stopwatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Elapsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalSeconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resultTags&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6.2 The SRE Dashboard for AI Systems
&lt;/h3&gt;

&lt;p&gt;Beyond the instrumentation code, the operational posture requires purpose-built dashboards that surface AI-specific signals:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost Burn Rate Dashboard&lt;/strong&gt;  — Real-time token consumption and estimated cost, broken down by tenant, plugin, and model. Alert when daily burn rate projects to exceed monthly budget. This is the FinOps control plane.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quality Degradation Dashboard&lt;/strong&gt;  — Trending quality scores from the semantic evaluation pipeline. A sudden drop in average confidence or grounding scores is often the first signal of a prompt regression before users start complaining.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Latency Percentile Dashboard&lt;/strong&gt;  — P50, P95, P99 invocation latency by plugin and model. LLM latency is highly variable; mean latency is a misleading metric. The P99 determines actual user experience under load.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fallback Activation Rate&lt;/strong&gt;  — Track the ratio of primary model invocations to fallback invocations. A rising fallback rate signals primary model degradation before the circuit breaker fully opens.&lt;/p&gt;

&lt;h3&gt;
  
  
  VII. Bringing It Together: The Production Architecture Blueprint
&lt;/h3&gt;

&lt;p&gt;A production Semantic Kernel deployment for an enterprise AI agent system has the following high-level architecture:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ingress &amp;amp; Auth Layer&lt;/strong&gt;  — API Gateway (Azure API Management) handling authentication, per-tenant rate limiting, and request routing. Token budget checks happen at this layer before requests reach the Semantic Kernel service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Semantic Kernel Service&lt;/strong&gt;  — Horizontally scaled .NET 9.0 service hosting the kernel, plugins, and filter pipeline. Stateless — all state lives in the memory layer or distributed cache.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Semantic Cache&lt;/strong&gt;  — Redis Cluster with vector similarity index (RedisStack) for semantic deduplication. Shared across all kernel service instances.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vector Memory Store&lt;/strong&gt;  — Azure AI Search or Qdrant for persistent semantic memory. Supports the RAG pipeline for knowledge-grounded responses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LLM Providers&lt;/strong&gt;  — Primary Azure OpenAI deployment (GPT-4o) and fallback deployment (GPT-4o-mini), each behind Polly resilience pipelines with independent circuit breakers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Observability Stack&lt;/strong&gt;  — OpenTelemetry Collector receiving traces and metrics from all services, forwarding to Azure Monitor + Application Insights. Grafana dashboards surfacing the AI-specific signals described in Section VI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evaluation Pipeline&lt;/strong&gt;  — Asynchronous worker service that samples production invocations, runs them through quality evaluation (LLM-as-judge pattern), and writes scores to the metrics store for dashboard visibility.&lt;/p&gt;

&lt;h3&gt;
  
  
  VIII. Key Takeaways and What’s Next
&lt;/h3&gt;

&lt;p&gt;Semantic Kernel earns its complexity. For teams moving LLM applications from proof-of-concept to production scale, the framework provides:&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;plugin architecture&lt;/strong&gt; that makes AI capabilities composable, testable, and maintainable across large teams. The &lt;strong&gt;planner&lt;/strong&gt; that enables autonomous AI agents without requiring every orchestration path to be pre-coded. The &lt;strong&gt;memory system&lt;/strong&gt; that makes RAG a first-class architectural pattern rather than a hand-rolled feature. The &lt;strong&gt;filter pipeline&lt;/strong&gt; that gives you the cross-cutting concerns — caching, rate limiting, observability, output validation — as a structured composition point rather than scattered middleware. And the &lt;strong&gt;multi-model connector architecture&lt;/strong&gt; that decouples business logic from any specific provider’s API surface.&lt;/p&gt;

&lt;p&gt;None of these benefits come for free. Semantic Kernel introduces genuine complexity: asynchronous orchestration, distributed state management, probabilistic failure modes, and token cost economics all demand architectural attention that simpler integrations can defer. The payoff is a system architecture that can survive the transition from demo to production, from hundreds of users to millions, and from the first AI feature to the tenth.&lt;/p&gt;

&lt;p&gt;In Part 2, we will move from foundations to implementation: building the async-first parallel orchestration patterns that collapse multi-step LLM workflows from sequential to concurrent execution, implementing the semantic cache with Redis vector similarity search, and walking through the full filter pipeline implementation with production-grade token metering.&lt;/p&gt;

</description>
      <category>finops</category>
      <category>enterpriseai</category>
      <category>agents</category>
      <category>sre</category>
    </item>
    <item>
      <title>AI-Powered Code Generation and Testing in .NET:</title>
      <dc:creator>Ali Suleyman TOPUZ</dc:creator>
      <pubDate>Sat, 21 Mar 2026 18:47:24 +0000</pubDate>
      <link>https://forem.com/topuzas/ai-powered-code-generation-and-testing-in-net-3af1</link>
      <guid>https://forem.com/topuzas/ai-powered-code-generation-and-testing-in-net-3af1</guid>
      <description>&lt;h3&gt;
  
  
  AI-Powered Code Generation and Testing in .NET: A Staff Engineer’s Guide to Modernizing Development Workflows
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;This guide reflects patterns and practices from production .NET deployments integrating OpenAI APIs. Code examples target .NET 9 and Azure OpenAI SDK. Model pricing and API specifications should be verified against current OpenAI and Azure documentation, as these change frequently.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Executive Summary
&lt;/h3&gt;

&lt;p&gt;The integration of artificial intelligence into software development represents one of the most profound shifts in our industry since the advent of high-level programming languages. Large Language Models (LLMs), particularly those from OpenAI, have moved from experimental novelties to production-critical components in many organizations’ development pipelines. Teams that successfully integrate AI assistants are reporting 30–55% productivity gains in specific development tasks, while those that fail to adapt are accumulating technical debt at an alarming rate.&lt;/p&gt;

&lt;p&gt;What makes this transition particularly significant is that AI isn’t simply accelerating existing workflows — it’s fundamentally restructuring them. Traditional development followed a relatively linear path: requirements gathering, design, implementation, testing, and deployment. AI introduces feedback loops at every stage, enabling developers to iterate on designs through conversational interfaces, generate boilerplate code from natural language specifications, and create comprehensive test suites that would have taken weeks to develop manually.&lt;/p&gt;

&lt;p&gt;From a Staff Engineer’s perspective, the challenge isn’t &lt;em&gt;whether&lt;/em&gt; to adopt AI-powered development tools, but &lt;em&gt;how&lt;/em&gt; to integrate them in a way that enhances rather than compromises system reliability. The modern development process must balance AI’s generative capabilities with human oversight and architectural discipline — establishing clear boundaries for where AI-assisted generation is appropriate, implementing robust validation frameworks, and maintaining the engineering rigor that ensures systems remain operable at scale. Organizations that treat AI as a force multiplier for experienced engineers, rather than a replacement for fundamental software engineering principles, are seeing sustainable productivity improvements. Those that don’t are discovering that AI can amplify bad practices just as effectively as good ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part I: The Business Case — FinOps and the Economics of AI-Assisted Development
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Reframing the ROI Calculation
&lt;/h3&gt;

&lt;p&gt;The economics of AI-powered code generation are compelling but require nuanced analysis. A senior .NET developer’s fully loaded cost (salary, benefits, overhead) typically ranges from $150,000 to $250,000 annually in major tech markets. If AI-assisted code generation reduces time spent on boilerplate by even 20%, that represents $30,000–$50,000 in recovered value per developer per year — value redirectable toward architectural decisions, performance optimization, and innovation.&lt;/p&gt;

&lt;p&gt;However, this calculation only holds if the quality of generated code meets production standards. The hidden costs of poor AI integration erode these gains faster than most organizations anticipate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hidden Costs and the New Technical Debt Profile
&lt;/h3&gt;

&lt;p&gt;AI-generated code creates a new class of technical debt that’s subtler and more expensive to remediate than traditional quality issues. Specifically:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shallow correctness&lt;/strong&gt; : Code passes basic tests but lacks defensive programming, proper error handling, idiomatic patterns, and observability hooks. It works in development but fails gracefully in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context blindness&lt;/strong&gt; : AI models generate stateless completions. They don’t understand your team’s specific architectural decisions, internal libraries, or the non-obvious constraints baked into existing systems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test hallucinations&lt;/strong&gt; : Generated tests can have high coverage metrics while testing nothing of value — asserting implementation details rather than behavior, or passing against buggy code because the model trained on the same buggy patterns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Amplification of bad prompting&lt;/strong&gt; : A developer who writes imprecise requirements gets imprecise code. The quality of the output is ceiling-bounded by the quality of the input. This shifts the critical skill from “writing code” to “specifying requirements precisely.”&lt;/p&gt;

&lt;h3&gt;
  
  
  FinOps Token Economics
&lt;/h3&gt;

&lt;p&gt;At scale, API costs become a first-order engineering concern. Key dimensions:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Token consumption patterns.&lt;/strong&gt; Input tokens (prompts + context) are typically cheaper than output tokens. For code generation, the ratio of input to output matters significantly. A well-engineered prompt that delivers tight, complete context will outperform a verbose one that wastes the context window on irrelevant information.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caching strategies.&lt;/strong&gt; System prompts and static context (coding standards, architectural patterns, common interfaces) should be leveraged with prompt caching where available. OpenAI’s caching can reduce costs by up to 50% on repeated prompt prefixes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model tiering.&lt;/strong&gt; Not all tasks require GPT-4-class models. A cost-optimized workflow routes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simple completions and boilerplate → GPT-4o-mini or equivalent&lt;/li&gt;
&lt;li&gt;Complex multi-file generation, architectural reasoning → GPT-4 Turbo or GPT-4o&lt;/li&gt;
&lt;li&gt;Real-time inline completion → the lowest-latency, lowest-cost model available&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Rate limit budgeting.&lt;/strong&gt; OpenAI enforces tokens-per-minute (TPM) and requests-per-minute (RPM) limits. At scale, these become constraints that require queuing, prioritization, and graceful degradation strategies — the same patterns you’d apply to any external dependency.&lt;/p&gt;

&lt;h3&gt;
  
  
  Part II: Architectural Deep Dive — How AI Code Generation Actually Works
&lt;/h3&gt;

&lt;h3&gt;
  
  
  The Transformer Architecture and Why It Matters for Engineers
&lt;/h3&gt;

&lt;p&gt;Modern LLMs are trained on vast corpora of code repositories, documentation, and natural language. The transformer architecture, with its self-attention mechanisms, captures long-range dependencies in code that earlier models missed — understanding that a variable declared 300 lines ago is relevant to the function being completed now.&lt;/p&gt;

&lt;p&gt;Training occurs in three phases, each of which has practical implications for how you use these models:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pre-training&lt;/strong&gt; ingests billions of tokens from public repositories (GitHub, GitLab, Stack Overflow). This gives the model broad knowledge of programming patterns across languages. It also means the model has learned from poor-quality code — a pre-trained model isn’t a source of ground truth on best practices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fine-tuning&lt;/strong&gt; on domain-specific datasets refines capabilities for particular frameworks — .NET-specific patterns, ASP.NET Core middleware conventions, Entity Framework idioms. When OpenAI fine-tunes on high-quality .NET code, it meaningfully improves output for that ecosystem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reinforcement Learning from Human Feedback (RLHF)&lt;/strong&gt; uses human evaluators to rank outputs based on correctness, efficiency, and adherence to best practices. This phase optimizes for production-grade code rather than merely syntactically correct code. Understanding this pipeline explains why model behavior can shift between versions even for identical prompts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Inference-Time Parameters: A Practitioner’s Guide
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Temperature (0.0–2.0):&lt;/strong&gt; Controls output randomness. For production code generation, 0.2–0.4 is the practical range. Higher values introduce creative variation that’s appropriate for exploratory prototyping but inappropriate for deterministic infrastructure code. Setting temperature to 0 doesn’t guarantee idempotent outputs due to floating-point sampling, but it gets close.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context window management:&lt;/strong&gt; Modern models support 8K–128K tokens, but longer contexts increase both latency and cost non-linearly. Effective context engineering — providing the right information rather than all information — is as important as prompt phrasing. For .NET development, this means including relevant interfaces, existing method signatures, and architectural constraints, not the entire solution file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nucleus sampling (top-p):&lt;/strong&gt; Constrains sampling to the most probable token pool. A value of 0.95 with temperature 0.3 works well for most code generation tasks. Avoid tuning both temperature and top-p simultaneously.&lt;/p&gt;

&lt;h3&gt;
  
  
  OpenAI Model Selection for .NET Workflows
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;| Scenario | Recommended Model | Rationale |
|---------------------------------------------|---------------------------|------------------------------------------------------------------|
| Large codebase understanding, multi-file generation | GPT-4 Turbo (128K context) | Context window enables full solution understanding |
| Real-time inline completion | GPT-4o | Sub-500ms latency viable for IDE integration |
| Boilerplate, CRUD, test scaffolding | GPT-4o-mini | Cost optimization without meaningful quality loss |
| Batch test generation (CI/CD) | GPT-4 Turbo | Throughput over latency; quality matters for test suite health |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Large codebase understanding, multi-file generation GPT-4 Turbo (128K context) Context window enables full solution understanding Real-time inline completion GPT-4o Sub-500ms latency viable for IDE integration Boilerplate, CRUD, test scaffolding GPT-4o-mini Cost optimization without meaningful quality loss Batch test generation (CI/CD) GPT-4 Turbo Throughput over latency; quality matters for test suite health&lt;/p&gt;

&lt;h3&gt;
  
  
  Part III: Implementation — Building Production-Grade AI Workflows in .NET
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Foundation: Dependency Injection and Configuration
&lt;/h3&gt;

&lt;p&gt;Production AI integration in .NET starts with proper infrastructure. The following pattern uses .NET 9 with the Azure OpenAI SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Program.cs&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.DependencyInjection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.Hosting&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Azure.AI.OpenAI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Azure&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateApplicationBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IConfiguration&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI:Endpoint"&lt;/span&gt;&lt;span class="p"&gt;]!);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;apiKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI:ApiKey"&lt;/span&gt;&lt;span class="p"&gt;]!;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OpenAIClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AzureKeyCredential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ICodeGenerationService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CodeGenerationService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IAITestGenerationService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AITestGenerationService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddMemoryCache&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IRateLimiter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TokenBucketRateLimiter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RunAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key architectural decisions here: the OpenAIClient is registered as a singleton (thread-safe, connection-pooled), caching is included from the start (not added later), and rate limiting is a first-class dependency.&lt;/p&gt;

&lt;h3&gt;
  
  
  Core Code Generation Service
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Services/CodeGenerationService.cs&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ICodeGenerationService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CodeGenerationResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GenerateCodeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;CodeGenerationRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;CodeGenerationRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Language&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"csharp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;MaxTokens&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;Temperature&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Dictionary&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;?&lt;/span&gt; &lt;span class="n"&gt;Context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;CodeGenerationResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;GeneratedCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;Suggestions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;TokensUsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;TimeSpan&lt;/span&gt; &lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;FromCache&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CodeGenerationService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ICodeGenerationService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;OpenAIClient&lt;/span&gt; &lt;span class="n"&gt;_openAIClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IMemoryCache&lt;/span&gt; &lt;span class="n"&gt;_cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CodeGenerationService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;IRateLimiter&lt;/span&gt; &lt;span class="n"&gt;_rateLimiter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;DeploymentName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"gpt-4"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;CodeGenerationService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;OpenAIClient&lt;/span&gt; &lt;span class="n"&gt;openAIClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;IMemoryCache&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CodeGenerationService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;IRateLimiter&lt;/span&gt; &lt;span class="n"&gt;rateLimiter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_openAIClient&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;openAIClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_cache&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_logger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;_rateLimiter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rateLimiter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CodeGenerationResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GenerateCodeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;CodeGenerationRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;startTime&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cacheKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;GenerateCacheKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TryGetValue&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CodeGenerationResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cachedResult&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Cache hit for prompt: {Prompt}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Prompt&lt;/span&gt;&lt;span class="p"&gt;[..&lt;/span&gt;&lt;span class="n"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Prompt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;)]);&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cachedResult&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;FromCache&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// Rate limit before calling external API&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_rateLimiter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AcquireAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ChatCompletionsOptions&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;DeploymentName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DeploymentName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Messages&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ChatRequestSystemMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;BuildSystemPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Language&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
                    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ChatRequestUserMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;BuildUserPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="n"&gt;MaxTokens&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Temperature&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Temperature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;NucleusSamplingFactor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.95f&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_openAIClient&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetChatCompletionsAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;choice&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;Value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
            &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;CodeGenerationResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;GeneratedCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;ExtractCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;choice&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;span class="n"&gt;Suggestions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;ExtractSuggestions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;choice&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;span class="n"&gt;TokensUsed&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;Value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UtcNow&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;startTime&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;_cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&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;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromMinutes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RequestFailedException&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&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="m"&gt;429&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogWarning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Rate limit hit; propagating for retry policy"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AIRateLimitException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"OpenAI rate limit exceeded"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&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;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;BuildSystemPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;$"""
&lt;/span&gt;        &lt;span class="n"&gt;You&lt;/span&gt; &lt;span class="n"&gt;are&lt;/span&gt; &lt;span class="n"&gt;an&lt;/span&gt; &lt;span class="n"&gt;expert&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;developer&lt;/span&gt; &lt;span class="n"&gt;following&lt;/span&gt; &lt;span class="n"&gt;production&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;grade&lt;/span&gt; &lt;span class="n"&gt;engineering&lt;/span&gt; &lt;span class="n"&gt;standards&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
        &lt;span class="n"&gt;Generate&lt;/span&gt; &lt;span class="n"&gt;clean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;maintainable&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Comprehensive&lt;/span&gt; &lt;span class="n"&gt;XML&lt;/span&gt; &lt;span class="n"&gt;documentation&lt;/span&gt; &lt;span class="n"&gt;comments&lt;/span&gt;
        &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Null&lt;/span&gt; &lt;span class="n"&gt;safety&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;defensive&lt;/span&gt; &lt;span class="n"&gt;programming&lt;/span&gt;
        &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Structured&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt; &lt;span class="n"&gt;via&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;
        &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Async&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;proper&lt;/span&gt; &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;propagation&lt;/span&gt;
        &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Input&lt;/span&gt; &lt;span class="n"&gt;validation&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;meaningful&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;
        &lt;span class="n"&gt;Always&lt;/span&gt; &lt;span class="n"&gt;provide&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;complete&lt;/span&gt; &lt;span class="n"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt; &lt;span class="n"&gt;Do&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="n"&gt;truncate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
        &lt;span class="s"&gt;""";
&lt;/span&gt;    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;BuildUserPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CodeGenerationRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;contextSection&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s"&gt;$"\n\nContext:\n&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\n"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kv&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;kv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Prompt&lt;/span&gt;&lt;span class="p"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;contextSection&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&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;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;GenerateCacheKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CodeGenerationRequest&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;Convert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToHexString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SHA256&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HashData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTF8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Prompt&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Language&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Temperature&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;ExtractCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;Regex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Match&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;span class="s"&gt;@"```

(?:\w+)?\n([\s\S]*?)

```"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Groups&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="nf"&gt;ExtractSuggestions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;Regex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Matches&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;span class="s"&gt;@"(?:NOTE|WARNING|CONSIDER):\s*(.+)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
             &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Groups&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
             &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToArray&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;h3&gt;
  
  
  AI-Powered Test Generation
&lt;/h3&gt;

&lt;p&gt;Test generation is where AI delivers disproportionate value. Writing comprehensive unit and integration tests is time-consuming and often deprioritized under delivery pressure. AI can generate a solid test scaffolding that developers then validate and extend.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Services/AITestGenerationService.cs&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IAITestGenerationService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TestGenerationResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GenerateTestsAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;sourceCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;TestGenerationOptions&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;TestGenerationOptions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;TestFramework&lt;/span&gt; &lt;span class="n"&gt;Framework&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TestFramework&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;XUnit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;IncludeEdgeCases&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;IncludeNegativeTests&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;GenerateMocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]?&lt;/span&gt; &lt;span class="n"&gt;FocusAreas&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;TestGenerationResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;TestCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;TestCoverageEstimate&lt;/span&gt; &lt;span class="n"&gt;Coverage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;GeneratedTestNames&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;TokensUsed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AITestGenerationService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IAITestGenerationService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;OpenAIClient&lt;/span&gt; &lt;span class="n"&gt;_openAIClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AITestGenerationService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;DeploymentName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"gpt-4"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TestGenerationResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GenerateTestsAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;sourceCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;TestGenerationOptions&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;BuildTestGenerationPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sourceCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;chatOptions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ChatCompletionsOptions&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;DeploymentName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DeploymentName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Messages&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ChatRequestSystemMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;GetTestSystemPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Framework&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ChatRequestUserMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;MaxTokens&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Temperature&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.2f&lt;/span&gt; &lt;span class="c1"&gt;// Low temperature for deterministic test logic&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_openAIClient&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetChatCompletionsAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chatOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;content&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;Value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&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;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;testCode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ExtractTestCode&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;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;testNames&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;ExtractTestMethodNames&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;testCode&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Generated {Count} tests for source ({Length} chars) using {Tokens} tokens"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;testNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sourceCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&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;Value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalTokens&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;TestGenerationResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;TestCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;testCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Coverage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;EstimateCoverage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sourceCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;testCode&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;GeneratedTestNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;testNames&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;TokensUsed&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;Value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalTokens&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;GetTestSystemPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestFramework&lt;/span&gt; &lt;span class="n"&gt;framework&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s"&gt;$"""
&lt;/span&gt;        &lt;span class="n"&gt;You&lt;/span&gt; &lt;span class="n"&gt;are&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;senior&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NET&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="n"&gt;engineer&lt;/span&gt; &lt;span class="n"&gt;specializing&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;framework&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="n"&gt;suites&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
        &lt;span class="n"&gt;Write&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt; &lt;span class="n"&gt;that&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Test&lt;/span&gt; &lt;span class="n"&gt;behavior&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="n"&gt;implementation&lt;/span&gt;
        &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Use&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;Arrange&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Act&lt;/span&gt;&lt;span class="p"&gt;-&lt;/span&gt;&lt;span class="n"&gt;Assert&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;clear&lt;/span&gt; &lt;span class="n"&gt;section&lt;/span&gt; &lt;span class="n"&gt;comments&lt;/span&gt;
        &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Use&lt;/span&gt; &lt;span class="n"&gt;meaningful&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="n"&gt;names&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;MethodName_StateUnderTest_ExpectedBehavior&lt;/span&gt;
        &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Cover&lt;/span&gt; &lt;span class="n"&gt;happy&lt;/span&gt; &lt;span class="n"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edge&lt;/span&gt; &lt;span class="n"&gt;cases&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="n"&gt;scenarios&lt;/span&gt;
        &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Use&lt;/span&gt; &lt;span class="n"&gt;NSubstitute&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nf"&gt;mocking&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="n"&gt;Moq&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Assert&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;observability&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;verify&lt;/span&gt; &lt;span class="n"&gt;ILogger&lt;/span&gt; &lt;span class="n"&gt;calls&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="n"&gt;paths&lt;/span&gt;
        &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Never&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt; &lt;span class="n"&gt;through&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;contracts&lt;/span&gt;
        &lt;span class="s"&gt;""";
&lt;/span&gt;    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;BuildTestGenerationPrompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;sourceCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TestGenerationOptions&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sb&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;StringBuilder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AppendLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Generate comprehensive tests for the following code:"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AppendLine&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AppendLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"```
&lt;/span&gt;&lt;span class="p"&gt;{%&lt;/span&gt; &lt;span class="n"&gt;endraw&lt;/span&gt; &lt;span class="p"&gt;%}&lt;/span&gt;
&lt;span class="n"&gt;csharp&lt;/span&gt;&lt;span class="s"&gt;");
&lt;/span&gt;        &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AppendLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sourceCode&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AppendLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"
&lt;/span&gt;&lt;span class="p"&gt;{%&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="p"&gt;%}&lt;/span&gt;
&lt;span class="err"&gt;```&lt;/span&gt;&lt;span class="s"&gt;");
&lt;/span&gt;        &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AppendLine&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FocusAreas&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AppendLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;$"Focus especially on: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;", "&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FocusAreas&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IncludeEdgeCases&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AppendLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Include edge cases: null inputs, empty collections, boundary values."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IncludeNegativeTests&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AppendLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Include negative tests: invalid inputs, exception scenarios, timeout behavior."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GenerateMocks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AppendLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Generate NSubstitute mocks for all external dependencies."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;ExtractTestCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;Regex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Match&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;span class="s"&gt;@"```

(?:csharp)?\n([\s\S]*?)

```"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Groups&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="nf"&gt;ExtractTestMethodNames&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;testCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;Regex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;testCode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;@"\[(?:Fact|Theory|Test)\][\s\S]*?(?:public|private)\s+\w+\s+(\w+)\s*\("&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
             &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Groups&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
             &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToArray&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;TestCoverageEstimate&lt;/span&gt; &lt;span class="nf"&gt;EstimateCoverage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;MethodsCovered&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;CountMethodsInTests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;EstimatedLineCoverage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;EstimateLineCoverage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;HasEdgeCases&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"null"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"empty"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StringComparison&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrdinalIgnoreCase&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;HasExceptionTests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Assert.Throws"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"await Assert.ThrowsAsync"&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;h3&gt;
  
  
  Part IV: Resilience Patterns — SRE Considerations for AI-Integrated Systems
&lt;/h3&gt;

&lt;h3&gt;
  
  
  The External Dependency Problem
&lt;/h3&gt;

&lt;p&gt;Integrating OpenAI into a development pipeline or runtime system introduces an external dependency with distinct failure characteristics. Unlike internal services, you don’t control its availability, latency distribution, or rate limits. From an SRE perspective, you must design for:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Availability&lt;/strong&gt; : OpenAI’s SLAs are not enterprise-grade guarantees. Your system’s critical paths cannot depend on OpenAI availability unless you’ve architected for graceful degradation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Latency variance&lt;/strong&gt; : GPT-4-class models can respond in 500ms or 15,000ms depending on load. Any synchronous dependency on these calls will cause tail latency issues in user-facing systems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rate limiting&lt;/strong&gt; : Token and request quotas are hard limits. Exceeding them returns 429 errors that, if unhandled, cascade through your system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resilience Implementation with Polly
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Infrastructure/AIResiliencePolicy.cs&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AIResiliencePolicy&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;ResiliencePipeline&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CodeGenerationResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ILogger&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;ResiliencePipelineBuilder&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CodeGenerationResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;RetryStrategyOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CodeGenerationResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;MaxRetryAttempts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;Delay&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;BackoffType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DelayBackoffType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Exponential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;UseJitter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;ShouldHandle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;PredicateBuilder&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CodeGenerationResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AIRateLimitException&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Handle&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HttpRequestException&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(),&lt;/span&gt;
                &lt;span class="n"&gt;OnRetry&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogWarning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="s"&gt;"AI call retry {Attempt} after {Delay}ms"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AttemptNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RetryDelay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalMilliseconds&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletedTask&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="nf"&gt;AddCircuitBreaker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;CircuitBreakerStrategyOptions&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CodeGenerationResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;FailureRatio&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;SamplingDuration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;MinimumThroughput&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;BreakDuration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;60&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;OnOpened&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"AI circuit breaker opened; falling back to non-AI path"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ValueTask&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CompletedTask&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="nf"&gt;AddTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TimeSpan&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromSeconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&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;h3&gt;
  
  
  Fallback Strategy
&lt;/h3&gt;

&lt;p&gt;When the AI service is unavailable, degrade gracefully rather than failing hard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Fallback to template-based generation when AI is unavailable&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ResilientCodeGenerationService&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ICodeGenerationService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ICodeGenerationService&lt;/span&gt; &lt;span class="n"&gt;_aiService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ITemplateCodeGenerationService&lt;/span&gt; &lt;span class="n"&gt;_templateService&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ResiliencePipeline&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CodeGenerationResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_policy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CodeGenerationResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GenerateCodeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;CodeGenerationRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecuteAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_aiService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GenerateCodeAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;cancellationToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BrokenCircuitException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Circuit is open; fall back to deterministic template generation&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_templateService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GenerateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancellationToken&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Part V: Observability — Measuring What Matters
&lt;/h3&gt;

&lt;h3&gt;
  
  
  The Metrics That Drive Decisions
&lt;/h3&gt;

&lt;p&gt;For AI-powered development workflows, standard application metrics are necessary but insufficient. You need a second layer of AI-specific metrics:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost and efficiency metrics:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Token consumption per request (input vs. output)&lt;/li&gt;
&lt;li&gt;Cost per code generation request (by model tier)&lt;/li&gt;
&lt;li&gt;Cache hit rate (target: &amp;gt;40% for repeated patterns)&lt;/li&gt;
&lt;li&gt;Token utilization efficiency (useful output tokens / total tokens billed)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Quality metrics:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generated code acceptance rate (what percentage of AI suggestions developers keep without major modification)&lt;/li&gt;
&lt;li&gt;Test pass rate for AI-generated tests on first run&lt;/li&gt;
&lt;li&gt;Post-merge defect rate for AI-assisted code vs. manually written code&lt;/li&gt;
&lt;li&gt;Code review comments per AI-generated PR&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Reliability metrics:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API error rate (4xx, 5xx by type)&lt;/li&gt;
&lt;li&gt;P50/P95/P99 latency for AI calls&lt;/li&gt;
&lt;li&gt;Circuit breaker trip frequency&lt;/li&gt;
&lt;li&gt;Fallback invocation rate&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  OpenTelemetry Integration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Observability/AIMetricsCollector.cs&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AIMetricsCollector&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Meter&lt;/span&gt; &lt;span class="n"&gt;_meter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_requestCounter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Histogram&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_latencyHistogram&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_tokenCounter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_cacheHitCounter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;ObservableGauge&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_estimatedCostGauge&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;_sessionCost&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;AIMetricsCollector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IMeterFactory&lt;/span&gt; &lt;span class="n"&gt;meterFactory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;_meter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;meterFactory&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="s"&gt;"AICodeGeneration"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;_requestCounter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_meter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateCounter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"ai.requests.total"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Total AI code generation requests"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;_latencyHistogram&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_meter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateHistogram&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"ai.request.duration_ms"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;unit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"ms"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"AI request latency distribution"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;_tokenCounter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_meter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateCounter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"ai.tokens.consumed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Total tokens consumed by model and type"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;_cacheHitCounter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_meter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateCounter&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"ai.cache.hits"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Cache hits avoiding API calls"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;_estimatedCostGauge&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_meter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateObservableGauge&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"ai.session.estimated_cost_usd"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;_sessionCost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Estimated session cost in USD"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;RecordRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;fromCache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;durationMs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;inputTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;outputTokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;TagList&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"from_cache"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fromCache&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="n"&gt;_requestCounter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;_latencyHistogram&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;durationMs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;TagList&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="n"&gt;_cacheHitCounter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fromCache&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;fromCache&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;_tokenCounter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;TagList&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"input"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
            &lt;span class="n"&gt;_tokenCounter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outputTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;TagList&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"output"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
            &lt;span class="n"&gt;_sessionCost&lt;/span&gt; &lt;span class="p"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;CalculateCost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inputTokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;outputTokens&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;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="nf"&gt;CalculateCost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"gpt-4"&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;0.00003&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;output&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;0.00006&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// $0.03/$0.06 per 1K&lt;/span&gt;
            &lt;span class="s"&gt;"gpt-4o"&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;0.000005&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;output&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="m"&gt;0.000015&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// $0.005/$0.015 per 1K&lt;/span&gt;
            &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&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;h3&gt;
  
  
  Part VI: Governance — Managing AI in Enterprise Environments
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Security and Compliance Architecture
&lt;/h3&gt;

&lt;p&gt;Code sent to external AI APIs leaves your infrastructure boundary. This has critical implications for enterprises operating under regulatory frameworks (SOC 2, HIPAA, PCI DSS, GDPR).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PII and secrets scrubbing.&lt;/strong&gt; Implement a mandatory pre-processing layer that redacts API keys, connection strings, PII, and other sensitive data before prompt submission. This is non-negotiable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CodeSanitizer&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;Regex&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;SensitivePatterns&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"(password|secret|apikey|token)\s*[=:]\s*['""]?[\w\-\.]+['""]?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;RegexOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IgnoreCase&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// email&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"\b\d{3}-\d{2}-\d{4}\b"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// SSN&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;@"\b\d{4}[\s-]\d{4}[\s-]\d{4}[\s-]\d{4}\b"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// credit card&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nf"&gt;Sanitize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&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;code&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SensitivePatterns&lt;/span&gt;&lt;span class="p"&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;pattern&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"[REDACTED]"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Azure OpenAI vs. OpenAI API.&lt;/strong&gt; Azure OpenAI offers data processing agreements (DPAs), regional data residency, and Private Link support that the public OpenAI API does not. For enterprise environments, Azure OpenAI is the correct architectural choice — not because of capability differences, but because of compliance posture.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prompt Governance Framework
&lt;/h3&gt;

&lt;p&gt;Uncontrolled prompt construction is both a security and quality risk. Implement centralized prompt management:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt versioning.&lt;/strong&gt; Treat prompts as first-class artifacts with versioning, testing, and rollback capability. A prompt change can alter output quality for thousands of developers. Store prompts in your configuration system with change history.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Output validation.&lt;/strong&gt; Generated code should pass through a validation pipeline before surfacing to developers or being committed. At minimum, validate syntactic correctness (Roslyn compiler) and run static analysis. Consider a secondary AI review pass for critical generation paths.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audit logging.&lt;/strong&gt; Log all AI interactions with sufficient context to audit decisions, diagnose quality regressions, and demonstrate compliance. Structure logs to support cost attribution by team and project.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Human-in-the-Loop Imperative
&lt;/h3&gt;

&lt;p&gt;AI code generation must be positioned as augmentation, not replacement. Governance policies should reflect this:&lt;/p&gt;

&lt;p&gt;Establish clear boundaries for where AI-generated code requires mandatory human review before merge. High-risk categories include authentication and authorization logic, cryptographic operations, data access and query construction, external API integrations, and infrastructure-as-code. For these categories, require that a reviewer explicitly affirms they have evaluated the generated code, not merely approved that “it looked fine.”&lt;/p&gt;

&lt;h3&gt;
  
  
  Part VII: Strategic Integration — Building the AI-Native Development Culture
&lt;/h3&gt;

&lt;h3&gt;
  
  
  Shifting the Skill Profile
&lt;/h3&gt;

&lt;p&gt;AI-assisted development shifts which skills are most valuable on engineering teams. The premium moves from “writing code quickly” to:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Requirements precision.&lt;/strong&gt; The limiting factor for AI code quality is how precisely requirements are specified. Engineers who can decompose ambiguous problems into precise, testable specifications will extract dramatically more value from AI tools.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Critical evaluation.&lt;/strong&gt; The ability to rapidly evaluate generated code for correctness, edge cases, security implications, and alignment with architectural patterns is more valuable than the ability to write the code from scratch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt engineering as a systems skill.&lt;/strong&gt; Writing effective prompts for code generation is a learnable, improvable skill. Teams should invest in building shared prompt libraries, running experiments to measure output quality, and codifying effective patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  CI/CD Integration Architecture
&lt;/h3&gt;

&lt;p&gt;AI code generation integrates most sustainably into the development workflow as a CI/CD-adjacent capability, not an ad-hoc interactive tool. Specific integration points:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pre-commit test generation.&lt;/strong&gt; Automatically generate test suggestions for changed files as part of the pre-commit hook. Surface these to developers for review rather than auto-committing — this drives quality without removing human judgment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PR description and review assistance.&lt;/strong&gt; Use AI to generate structured PR descriptions from diffs, flag potential issues, and suggest test coverage gaps. This accelerates review without replacing it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Technical debt detection.&lt;/strong&gt; Run AI analysis on modified code to flag technical debt patterns — missing error handling, lack of observability, unhandled async exceptions — as part of the CI pipeline. Surfacing these as advisory warnings (not blocking) builds awareness without creating friction.&lt;/p&gt;

&lt;h3&gt;
  
  
  Measuring Success and Iteration
&lt;/h3&gt;

&lt;p&gt;Define success metrics before deploying AI tooling, and measure them rigorously:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developer velocity metrics:&lt;/strong&gt; cycle time (PR open to merge), time from requirement to passing tests, and PR iteration count. Watch for counterintuitive results — AI tooling sometimes increases PR iteration count initially as review standards rise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quality metrics:&lt;/strong&gt; post-release defect rate for AI-assisted code, test coverage delta, and technical debt accumulation rate measured via static analysis trends.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adoption and satisfaction metrics:&lt;/strong&gt; active usage rate among the team, qualitative feedback on whether the tool is helping or hindering. Tools that developers circumvent provide zero value and erode trust in the initiative.&lt;/p&gt;

&lt;p&gt;Instrument everything from day one. The data you collect in the first 90 days will determine whether you’re investing in the right workflows or building elaborate tooling that no one uses.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;AI-powered code generation and testing represent a genuine structural shift in software development productivity — not incremental tooling improvement, but a change in the fundamental economics of building software. The teams that will succeed are those that approach this shift with the same architectural discipline, quality standards, and operational rigor they apply to any production system.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The core principles for sustainable AI integration in .NET environments: treat AI as an external dependency with real reliability characteristics; invest in prompt engineering as a first-class engineering discipline; maintain the human judgment layer for high-risk generation paths; instrument everything to drive continuous improvement; and position experienced engineers as force multipliers, not replacements.&lt;/p&gt;

&lt;p&gt;The technology will continue to improve rapidly. The architectural patterns, governance frameworks, and organizational disciplines described in this guide will remain relevant even as the underlying models evolve. Build the foundation right, and you’ll be positioned to capture successive waves of improvement as they arrive.&lt;/p&gt;

</description>
      <category>testautomation</category>
      <category>finops</category>
      <category>codegeneration</category>
      <category>sre</category>
    </item>
    <item>
      <title>Leading Through Technical Crisis: A Staff Engineer’s Guide to Architecture, Resilience, and…</title>
      <dc:creator>Ali Suleyman TOPUZ</dc:creator>
      <pubDate>Sat, 21 Mar 2026 18:47:11 +0000</pubDate>
      <link>https://forem.com/topuzas/leading-through-technical-crisis-a-staff-engineers-guide-to-architecture-resilience-and-4h9k</link>
      <guid>https://forem.com/topuzas/leading-through-technical-crisis-a-staff-engineers-guide-to-architecture-resilience-and-4h9k</guid>
      <description>&lt;h3&gt;
  
  
  Leading Through Technical Crisis: A Staff Engineer’s Guide to Architecture, Resilience, and Strategic Decision-Making
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;When systems fail, it’s not just your code that gets tested — it’s your judgment, your leadership, and your identity as an engineer.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There’s a particular kind of silence that falls over an engineering team when something goes catastrophically wrong in production. Slack channels that were humming with chatter thirty minutes ago suddenly fill with terse messages. Dashboards turn red. Someone types “I think it’s the database” and nobody laughs at the Occam’s razor of that statement because maybe — just maybe — it actually is the database.&lt;/p&gt;

&lt;p&gt;I’ve been in that silence more times than I’d like to admit. And what I’ve come to understand, through hard-won experience, is that technical crises are not primarily engineering problems. They are &lt;em&gt;leadership&lt;/em&gt; problems that happen to have engineering solutions. The distinction matters enormously, because the engineers who thrive in crisis aren’t necessarily the ones who write the cleanest code or know the most about distributed systems theory. They’re the ones who can hold their cognitive composure, orient a team under pressure, and make irreversible decisions with incomplete information — all while simultaneously debugging a distributed system that is actively lying to them.&lt;/p&gt;

&lt;p&gt;This is what it means to lead through technical crisis.&lt;/p&gt;

&lt;h3&gt;
  
  
  What We Mean When We Say “Crisis”
&lt;/h3&gt;

&lt;p&gt;Before we talk about strategy, it’s worth being precise about what a technical crisis actually is — because engineering culture often conflates “incident” with “crisis,” and that blurry boundary has real consequences for how teams respond.&lt;/p&gt;

&lt;p&gt;A production incident is a system behaving outside expected parameters. A technical crisis is something more specific: it’s the moment when a failure’s business impact &lt;em&gt;exceeds your team’s standard operating procedures&lt;/em&gt;. The system isn’t just down. Multiple things are failing in ways that amplify each other. The root cause isn’t clear. Recovery time is approaching or has already exceeded what your stakeholders can tolerate. The on-call runbook doesn’t have an entry for this particular flavor of disaster.&lt;/p&gt;

&lt;p&gt;This distinction matters because crises demand leadership intervention — not just technical execution. They require someone to be simultaneously debugging the system, managing upward communication, deciding which recovery path to pursue, allocating team resources, and maintaining the psychological safety of an exhausted team. These are not engineering tasks. They are &lt;em&gt;organizational&lt;/em&gt; tasks that require an engineer to perform them.&lt;/p&gt;

&lt;p&gt;The modern software landscape makes crises more likely, not less. The migration toward microservices, distributed databases, third-party LLM integrations, and cloud infrastructure creates systems of staggering complexity — systems where failures in one component cascade unpredictably into failures in components that had no business being affected. When your authentication service starts dropping 40% of requests, the knock-on effects through a system of 50 microservices can be nearly impossible to trace in real time. Understanding &lt;em&gt;why&lt;/em&gt; your system behaves this way under stress, and having the architectural and psychological tools to respond, is the difference between a two-hour recovery and a twelve-hour death march.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Psychological Architecture of Crisis Leadership
&lt;/h3&gt;

&lt;p&gt;Here’s something engineering culture is reluctant to discuss: crisis response is a stress-mediated cognitive activity, and stress &lt;em&gt;degrades&lt;/em&gt; exactly the cognitive functions you need most.&lt;/p&gt;

&lt;p&gt;When a production incident reaches crisis level, your body enters a state of heightened arousal. Your prefrontal cortex — the part responsible for working memory, risk assessment, and flexible decision-making — starts operating at reduced capacity. Your thinking becomes more rigid. You anchor harder on the first hypothesis that sounds plausible. You become worse at integrating information from multiple sources. Your time horizon collapses; the next five minutes feel more real than the next five hours.&lt;/p&gt;

&lt;p&gt;None of this is a character flaw. It’s biology. What separates experienced crisis leaders isn’t immunity to this response — it’s having built &lt;em&gt;systems&lt;/em&gt; to compensate for it.&lt;/p&gt;

&lt;p&gt;The most important system is a shared mental model. When every engineer on your incident response team has a common understanding of how your architecture behaves under stress, you reduce the cognitive load required to diagnose problems. You’re not rebuilding your understanding of the system from scratch under pressure; you’re pattern-matching against a map you’ve studied in calmer moments. This is why architectural review, game days, and postmortem culture aren’t just “nice to haves” — they are investments in your team’s cognitive infrastructure for exactly the moments when it will be most strained.&lt;/p&gt;

&lt;p&gt;The second system is vocabulary. When I tell my team “implement a circuit breaker on the LLM client,” there’s no ambiguity. We’ve talked about circuit breakers before. Everyone knows what it means, what it does, and roughly how to implement it. The design pattern has become a shorthand that bypasses the need for lengthy explanation under pressure. This is one of the least-discussed values of standardizing on architectural patterns across a codebase: not elegance, not theoretical purity, but &lt;em&gt;shared vocabulary for crisis moments&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The third system is clear role definition. During crisis, role ambiguity is catastrophic. Someone needs to be in charge of diagnosis. Someone needs to own stakeholder communication. Someone needs to have authority over deployment decisions. These roles don’t need to be formal or permanent — they can be assumed situationally — but they need to be explicit. Ambiguity about who’s driving creates the worst possible outcome: multiple engineers pulling in different directions, each acting on a different theory of the failure, each second-guessing the others.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architectural Decisions as Crisis Prevention
&lt;/h3&gt;

&lt;p&gt;The most powerful thing a Staff Engineer can do for crisis management is work that happens months or years before any incident occurs. Architectural decisions made during the calm of normal development either create or close off options during crisis. This is not metaphorical — it’s literally true that the design patterns chosen in sprint planning meetings determine whether an incident responder has a clean lever to pull at 2 AM.&lt;/p&gt;

&lt;p&gt;Consider the difference between two codebases. In the first, LLM provider API clients are instantiated in a dozen different places throughout the service layer, each with slightly different configuration, each with ad hoc retry logic, each failing in its own idiosyncratic way. In the second, all LLM client creation flows through a single factory, which checks provider health status, enforces rate limits, and integrates circuit breaker logic.&lt;/p&gt;

&lt;p&gt;When OpenAI returns 503s at 2 AM on a Friday, these two codebases have dramatically different failure modes. In the first, you’re hunting through scattered instantiation points trying to understand why some requests are failing and others aren’t, manually patching retry logic in multiple places. In the second, you have a single choke point where you can emergency-throttle requests, swap to a fallback provider, or disable a misbehaving model across fifty service instances with a configuration change.&lt;/p&gt;

&lt;p&gt;This is the Factory pattern at work. It seems like an engineering best practice with modest operational benefits in normal conditions. It becomes a survival tool in crisis conditions. The same logic applies across the architectural patterns that define resilient systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Patterns That Save You
&lt;/h3&gt;

&lt;p&gt;Not all design patterns are created equal in crisis scenarios. Some patterns are primarily about code organization or developer experience. Others are genuinely load-bearing under production stress. The ones that matter most are those that provide &lt;em&gt;control surface&lt;/em&gt; — meaningful levers that an incident responder can pull to change system behavior without a code deployment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Circuit Breakers&lt;/strong&gt; are the most important resilience pattern that teams consistently under-implement. A circuit breaker wraps calls to an external dependency — a third-party API, a downstream service, a database — and tracks failure rates. When failures exceed a configurable threshold, the circuit “trips” and subsequent calls fail immediately rather than waiting for timeout. This prevents cascading failures: when your payment processor is struggling, your checkout service stops accumulating threads waiting for responses that won’t come in time, which prevents your checkout service from taking down your recommendation service, which prevents your recommendation service from degrading your homepage. The cascade stops at the first circuit break rather than propagating through the entire system.&lt;/p&gt;

&lt;p&gt;The subtlety that teams miss is that circuit breakers need to be tunable in production. Thresholds that make sense under normal load may be wrong under crisis conditions. Half-open states need monitoring. This means your circuit breaker implementation needs observability baked in — not as an afterthought, but as a first-class feature.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Strategy Pattern&lt;/strong&gt; solves a different crisis problem: what happens when you need to change fundamental behavior without deploying code? If your primary LLM provider starts returning errors and you’ve hard-coded provider-specific logic throughout your service layer, switching to a fallback provider requires code changes, testing, and deployment — all under pressure, all with elevated risk of introducing new bugs. If you’ve encapsulated provider-specific behavior behind a strategy interface, you can swap implementations at runtime, driven by configuration or health check signals, with no deployment required.&lt;/p&gt;

&lt;p&gt;This pattern is particularly powerful in the current LLM landscape, where providers have meaningfully different rate limits, latency characteristics, and failure modes. A well-designed strategy implementation lets you route to OpenAI under normal conditions, fall back to Anthropic when OpenAI rate limits, and fall back to a local model when both are degraded — all transparently, all without the calling code knowing which provider is active.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bulkhead Patterns&lt;/strong&gt; apply the naval engineering insight that isolating compartments prevents a single breach from sinking the ship. In software, this means isolating resource pools — thread pools, connection pools, memory allocations — so that degradation in one area of the system cannot consume resources needed by other areas. Your LLM inference requests should not be competing for the same thread pool as your critical path authentication logic. Your batch processing jobs should have connection pool limits that prevent them from starving real-time user requests of database connections. These boundaries feel over-engineered until the moment they’re the only thing standing between you and a total system failure.&lt;/p&gt;

&lt;h3&gt;
  
  
  The CAP Theorem Isn’t Academic
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Every engineer has encountered the CAP theorem in a whiteboard interview or a distributed systems course. Fewer have encountered it at 3 AM while a distributed database is exhibiting split-brain behavior and business stakeholders are sending messages with increasing numbers of exclamation points.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The theorem states that a distributed system cannot simultaneously guarantee Consistency, Availability, and Partition Tolerance during a network partition. Since network partitions are not theoretical — they happen, in production, to everyone — you must choose: during a partition event, will your system favor consistency (potentially refusing requests to avoid serving stale or conflicting data) or availability (serving requests even when you cannot guarantee the data is current)?&lt;/p&gt;

&lt;p&gt;This is not an implementation detail. It is a product decision with business consequences, and it needs to be made explicitly and understood widely before an incident occurs. An e-commerce system that favors consistency might refuse to process orders during a partition event, showing users a 503 and losing revenue. A system that favors availability might process orders against stale inventory data, causing overselling that requires expensive manual reconciliation. Neither choice is wrong — but the choice needs to be &lt;em&gt;deliberate&lt;/em&gt;, documented, and understood by the people who will be making recovery decisions.&lt;/p&gt;

&lt;p&gt;The crisis leadership failure mode here is discovering, during an incident, that nobody on the team knows what the intended behavior is. Different engineers have different intuitions. Some want to restore consistency as quickly as possible; others are focused on bringing availability back online. Without explicit architectural decisions to anchor the conversation, teams waste precious recovery time relitigating product decisions under pressure.&lt;/p&gt;

&lt;p&gt;The Staff Engineer’s job is to ensure that these trade-offs are discussed, decided, and written down before they’re relevant. The postmortem is too late.&lt;/p&gt;

&lt;h3&gt;
  
  
  Observability: Seeing Before It Hurts
&lt;/h3&gt;

&lt;p&gt;There’s a meaningful difference between monitoring and observability, and understanding it is essential for crisis prevention. Monitoring tells you when things have gone wrong. Observability tells you &lt;em&gt;why&lt;/em&gt; they went wrong and — ideally — gives you signals before they go catastrophically wrong.&lt;/p&gt;

&lt;p&gt;Traditional monitoring is threshold-based: CPU above 80%, response time above 500ms, error rate above 1%. These metrics have their place, but they suffer from a fundamental limitation: they measure symptoms, not causes, and they measure them after the fact. By the time your error rate crosses 1%, customers have been experiencing failures for some time already.&lt;/p&gt;

&lt;p&gt;Observability, as a practice, means building your systems so that their internal state can be interrogated through their external outputs. This requires three types of telemetry working in concert: metrics (the quantitative health indicators that tell you something is wrong), logs (the contextual records that tell you what was happening when things went wrong), and distributed traces (the cross-service journey maps that show you how a failure propagated through your system).&lt;/p&gt;

&lt;p&gt;The implementation detail that makes observability useful in crisis scenarios is &lt;em&gt;correlation&lt;/em&gt;. When a customer reports a failed checkout at 14:23:47, you need to be able to trace that specific request across six services, correlating it with the database query that took 3 seconds, the downstream API call that returned a transient error, and the retry logic that eventually exhausted its budget. Without correlation identifiers threading through your logs and traces, you have data but not information.&lt;/p&gt;

&lt;p&gt;This is where structured logging — logging that emits machine-parseable records with consistent field names rather than free-text strings — pays enormous dividends. During crisis, the speed at which you can answer “what was happening in service X at time T for correlation ID Y” determines how fast you can form and test hypotheses. Structured logs, indexed and searchable, let you answer that question in seconds rather than minutes. Every minute saved in diagnosis is a minute saved in recovery.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Equally important is building observability that surfaces signals &lt;em&gt;before&lt;/em&gt; they become crises. This requires moving beyond reactive monitoring toward leading indicators: not just measuring error rates, but tracking the rate of change of error rates. Not just watching queue depth, but measuring how queue depth correlates with upstream request volume. Not just alerting on database connection pool exhaustion, but alerting when you’re at 70% of pool capacity and trending up. The goal is a system that gives you a ten-minute warning, not a ten-second one.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Making Decisions With Incomplete Information
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Here’s the hardest truth about crisis leadership: you will never have enough information to be certain your decision is right, and you will have to make the decision anyway.&lt;/p&gt;

&lt;p&gt;This sits badly with engineers. Our discipline trains us to be precise, to gather data before drawing conclusions, to avoid premature optimization. These instincts serve us well in normal development. In crisis, they can become pathological. The engineer who waits for certainty before acting is the engineer who lets a recoverable incident become an existential one.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The mental model that helps me most is thinking about reversibility. Every decision during crisis can be placed somewhere on a spectrum from fully reversible to fully irreversible. Enabling a feature flag is reversible. Disabling a service to stop cascading failures is mostly reversible. Deleting data is irreversible. Rolling back a database migration might be irreversible, or nearly so.&lt;/p&gt;

&lt;p&gt;For reversible decisions, speed matters more than certainty. Make the call, observe the results, adjust. The cost of being wrong and reversing is low; the cost of hesitation is high. For irreversible decisions, the calculus flips. Take the time you need to build confidence. Get a second opinion. Document your reasoning. The cost of being wrong and unable to reverse is potentially catastrophic.&lt;/p&gt;

&lt;p&gt;This framework also helps with a common crisis mistake: spending too long diagnosing when you should be mitigating. If you have a reversible mitigation available — rolling back a deployment, disabling a feature, routing traffic to a healthy region — there’s often wisdom in taking that action &lt;em&gt;before&lt;/em&gt; you fully understand the cause. Stop the bleeding, then diagnose. The pressure to understand before acting comes from a place of intellectual honesty, which is admirable, but it can extend customer impact unnecessarily. You can always do a thorough postmortem investigation once the system is stable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Communication as a Technical Skill
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;One of the most persistent misconceptions about technical crisis leadership is that communication is a soft skill layered on top of the hard technical work. This is wrong. In crisis conditions, communication &lt;em&gt;is&lt;/em&gt; the hard technical work.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The reason is that crisis recovery almost always involves multiple stakeholders with fundamentally different information needs and tolerance for uncertainty. Your engineers need detailed technical context: what’s failing, what’s been tried, what hypotheses are being tested. Your product leadership needs business impact framing: how many users are affected, what functionality is degraded, when will it be resolved. Your executive team needs confidence that the situation is under control and clear expectations about timeline. Each audience requires a different translation of the same underlying reality.&lt;/p&gt;

&lt;p&gt;Managing this translation, while simultaneously contributing to technical diagnosis and recovery, is a profound cognitive challenge. The leaders who do it well have developed a set of practices that reduce the cognitive overhead: standardized status update templates that can be filled in quickly, a designated communications lead for large incidents who is distinct from the technical lead, and an explicit commitment to updating stakeholders on a regular cadence (every 30 minutes, say) regardless of whether there’s new information. The last point matters enormously — silence in a crisis is interpreted as incompetence or dishonesty, even when the reality is simply that diagnosis is still underway.&lt;/p&gt;

&lt;p&gt;The other communication challenge is upward translation of technical debt and systemic risk. Crises are often symptoms of accumulated technical debt that was deprioritized in favor of feature development. After the immediate fire is out, the Staff Engineer or Principal Engineer who led the recovery has an opportunity — and arguably a responsibility — to translate what happened into business-language justification for the investment required to prevent recurrence. This is not complaining about technical debt. It’s strategic communication: connecting architectural risk to business outcomes in a language that enables leadership to make informed investment decisions.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Postmortem: Making Crises Count
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Every major incident contains the seeds of organizational improvement. Whether those seeds are cultivated or left to decay depends almost entirely on the quality of your postmortem practice.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A good postmortem is not a blame exercise. This sounds obvious and is, in practice, genuinely difficult to maintain, because the natural human instinct in the wake of failure is to identify who made the mistake. Blame-oriented postmortems are worse than useless: they cause engineers to be defensive rather than transparent, which systematically degrades the quality of the causal analysis, which means the same failure modes recur.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blameless postmortems — &lt;/strong&gt; the approach championed by Google’s SRE culture and widely adopted across the industry — operate on the premise that competent engineers working within a given system will make the decisions that the system makes it natural to make. When something goes wrong, the correct question is not “who made the mistake?” but “what was it about our system, our processes, or our information environment that made this mistake the natural thing to do?” The answer almost always points to something actionable: a missing validation, an ambiguous runbook, an alert that fires too late, an architectural assumption that turned out to be wrong.&lt;/p&gt;

&lt;p&gt;The output of a good postmortem is a set of concrete, time-bounded action items with clear ownership. Not “we should improve our monitoring” — that’s a sentiment, not a commitment. Instead: “By March 15th, we will add latency percentile alerting to the checkout service, owned by the platform team.” The specificity is what converts learning into change.&lt;/p&gt;

&lt;p&gt;Over time, postmortem culture compounds. Each incident investigation builds institutional knowledge about how your system fails. Each action item either reduces the likelihood of similar failures or improves your response capability when they occur. The organizations that emerge from crises stronger are the ones that treat each incident not as an embarrassment to be put behind them as quickly as possible, but as a funded investment in organizational learning.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Long Game: Building Resilient Teams
&lt;/h3&gt;

&lt;p&gt;Technical resilience and team resilience are not separate concerns. The systems that survive crises are built and maintained by teams that have learned how to function under pressure — and learning to function under pressure requires practice in conditions that are stressful but survivable.&lt;/p&gt;

&lt;p&gt;This is the rationale for game days and chaos engineering practices: deliberately inducing controlled failures to give teams practice at the cognitive and operational skills that crises demand, in conditions where the cost of imperfect performance is low. Teams that have run game days together develop the shared mental models, communication rhythms, and role clarity that make them dramatically more effective in real incidents. The first time your team practices diagnosing a simulated database failure should not be during a real one.&lt;/p&gt;

&lt;p&gt;Beyond operational practice, resilient teams share a cultural characteristic that is harder to engineer but equally important: psychological safety. Teams where engineers are afraid of blame, afraid of looking incompetent, or afraid of surfacing bad news are teams that are slow to escalate, slow to admit uncertainty, and slow to call for help. These behaviors make crises worse. The investment in building a culture where engineers feel safe saying “I don’t know,” “I was wrong,” or “I need help” pays compounding returns in crisis performance.&lt;/p&gt;

&lt;p&gt;As a Staff or Principal Engineer, you have more influence over this culture than you might think. The way you respond when a junior engineer reports a problem they created. The way you talk about your own mistakes in postmortems. The way you ask questions in code review. These behaviors model what the culture values, and the culture you model is the culture your team inherits.&lt;/p&gt;

&lt;h3&gt;
  
  
  Closing: The Crisis That Made You
&lt;/h3&gt;

&lt;p&gt;There’s a version of this essay that frames technical crisis management as a domain of pure competence — a set of techniques to be mastered and deployed. That version is incomplete.&lt;/p&gt;

&lt;p&gt;The deeper truth is that crises are formative experiences that fundamentally shape engineering leaders. They reveal what you actually believe about trade-offs, about people, about how systems should be built. They expose the gap between the engineer you aspire to be and the engineer you are under pressure. And they offer — if you’re willing to sit with the discomfort long enough — a clear view of what you need to develop.&lt;/p&gt;

&lt;p&gt;The leaders who emerge from major incidents wiser and more capable are almost never the ones who performed perfectly. They’re the ones who paid attention: to what they got right, to what they missed, to where their team struggled, and to what the system was trying to tell them about its own design. They treated the crisis not as a problem to be survived but as information to be processed.&lt;/p&gt;

&lt;p&gt;Build the systems that let you see clearly when things are going wrong. Build the architectural patterns that give you control surface when they do. Build the team culture that enables honest, fast response. And when the crisis comes — and it will — remember that your job is not to be the hero who single-handedly restores service. It’s to be the leader who brings clarity to chaos, confidence to uncertainty, and learning to failure.&lt;/p&gt;

&lt;p&gt;That’s what it means to lead through technical crisis.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If this resonated with you, I’d love to hear about your own experiences leading through production incidents. What patterns have saved you? What failures taught you the most? The conversation in the comments is often richer than the article.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>technicalleadership</category>
      <category>engineeringleadershi</category>
      <category>crisismanagement</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Accessibility-First Software Engineering: Building Inclusive Systems from the Ground Up</title>
      <dc:creator>Ali Suleyman TOPUZ</dc:creator>
      <pubDate>Sat, 21 Mar 2026 18:46:49 +0000</pubDate>
      <link>https://forem.com/topuzas/accessibility-first-software-engineering-building-inclusive-systems-from-the-ground-up-38ol</link>
      <guid>https://forem.com/topuzas/accessibility-first-software-engineering-building-inclusive-systems-from-the-ground-up-38ol</guid>
      <description>&lt;p&gt;&lt;em&gt;This article explores the architectural and cultural foundations of accessibility-first software engineering. The technical patterns discussed — including accessibility context propagation, multi-modal API design, and accessibility observability — represent practical starting points for teams beginning this work. WCAG 2.2 remains the primary technical reference standard, and hands-on testing with actual assistive technologies should accompany any automated tooling strategy.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What if the way we build software has been wrong this whole time — not morally wrong, but architecturally wrong?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There is a story engineers love to tell about curb cuts. When cities started installing those small concrete ramps at sidewalk corners to help wheelchair users navigate urban streets, something unexpected happened. Everyone started using them — parents pushing strollers, delivery workers hauling hand trucks, travelers dragging rolling luggage, cyclists, skaters, elderly pedestrians with canes. A design decision made for one specific group of people turned out to make life measurably better for nearly everyone.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Software has been ignoring this lesson for decades.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Accessibility in most engineering organizations is treated like a &lt;strong&gt;compliance obligation&lt;/strong&gt;  — a list of legal checkboxes to satisfy after the “real” product work is done. Teams ship features, build architectures, make foundational decisions, and then, somewhere near the end of a sprint cycle or just before a launch deadline, someone says: &lt;em&gt;we should probably make this accessible.&lt;/em&gt; At that point, the work becomes exponentially harder, exponentially more expensive, and almost always incomplete.&lt;/p&gt;

&lt;p&gt;Accessibility-first software engineering is the argument that this sequence is backwards. It is the practice of treating accessibility not as a feature layer but as an architectural constraint — one that belongs at the very beginning of design, right alongside performance, scalability, and security. And when you actually build software this way, something remarkable happens: you end up with better software, period.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Scale of the Problem We Are Ignoring
&lt;/h3&gt;

&lt;p&gt;Before getting into architecture and implementation, it is worth sitting with the numbers for a moment, because they have a way of reframing what “edge case” means.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The World Health Organization estimates that approximately 1.3 billion people — roughly 16% of the global population — experience significant disability.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But that figure captures only a fraction of the people affected by inaccessible software. Expand the lens to include temporary disabilities (a broken wrist, post-surgery recovery, an eye infection), situational limitations (using a phone in direct sunlight, operating a laptop while riding a bus, watching a video in a noisy open office), and age-related changes that affect virtually every person who lives long enough, and you are no longer talking about a minority use case. You are talking about every user, at some point in their relationship with your product.&lt;/p&gt;

&lt;p&gt;WebAIM publishes an annual analysis of the top one million websites on the internet. In recent years, that analysis has consistently found that over 96% of those homepages have detectable WCAG 2 failures. Nearly every major site on the internet is, to some degree, broken for a significant portion of its users. This is not a niche problem. It is a systemic failure embedded in how the industry thinks about and practices software development.&lt;/p&gt;

&lt;p&gt;The business case is real too, though it probably should not be the primary motivation. People with disabilities represent substantial purchasing power — estimates place discretionary income for working-age people with disabilities in the United States alone at around $21 billion annually. The global figure, including older adults and people experiencing temporary limitations, stretches into the trillions. Companies building genuinely accessible products are not just being ethical. They are also accessing a massively underserved market that their competitors are actively excluding.&lt;/p&gt;

&lt;p&gt;But if the ethical argument is sufficient — and it should be — let us start there. Millions of people are unable to fully use software that has become essential infrastructure for modern life: job applications, healthcare portals, banking systems, government services, educational platforms. Inaccessibility in software is not a minor inconvenience. For many people, it is a barrier to participation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Retrofitting Never Works (And Why We Keep Trying)
&lt;/h3&gt;

&lt;p&gt;Most accessibility work happens as retrofitting. A product ships. Legal risk appears — or worse, a lawsuit arrives. Engineers are tasked with making the existing system accessible. They work hard, they make improvements, but the result is almost never fully accessible because the foundational decisions have already been made and cannot be easily undone.&lt;/p&gt;

&lt;p&gt;The Department of Homeland Security’s Trusted Tester program has done work estimating the cost differential between addressing accessibility issues at different stages of the development lifecycle. Their findings point toward roughly a 10x cost multiplier: problems caught and fixed during the design phase cost about one-tenth of what they cost to fix once a product is in production. When you factor in the potential legal exposure — website accessibility lawsuits have historically settled in ranges from $35,000 to $75,000 before accounting for legal fees and remediation costs — the financial argument for building correctly from the start becomes overwhelming.&lt;/p&gt;

&lt;p&gt;But the problem runs deeper than cost. Retrofitting accessibility onto a poorly architected system often produces what might be called “accessibility theater” — the appearance of compliance without the reality of usable experience. You can add ARIA labels to semantically meaningless markup. You can make a keyboard focus indicator visible without making the keyboard navigation logical. You can include alt text on images while the surrounding interactive components remain completely opaque to screen readers. These patches satisfy automated scanning tools without actually serving the people who need accessible software.&lt;/p&gt;

&lt;p&gt;The reason retrofitting produces these hollow results is that genuine accessibility requires architectural decisions. It requires that your application expose meaningful state information. It requires that your interaction models support multiple input modalities from the ground up. It requires that your data layer returns everything needed to construct accessible interfaces. These are not features that can be sprinkled on top of an existing system. They have to be designed in.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Accessibility-First Architecture Actually Means
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Shifting to accessibility-first development is not primarily about learning a longer checklist or hiring specialists to review finished work. It is about a set of architectural principles that shape decisions from the very beginning.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Accessibility context must propagate through the system:&lt;/strong&gt; In traditional layered architectures, user preferences and accessibility metadata get dropped as requests cross service boundaries. A user who depends on reduced motion preferences, or who uses a screen reader that requires different response structures, or who needs extended session timeouts because motor impairments slow their interactions — that context needs to travel through every layer of the system. This is an explicit architectural requirement, not an implementation detail. The overhead is real but minimal, typically a few hundred bytes per request. The impact on users who depend on it is substantial.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Asynchronous patterns are not just a scalability optimization — they are an accessibility requirement:&lt;/strong&gt; Tightly coupled, synchronous architectures create barriers for users of assistive technologies that operate on different temporal scales than standard input devices. A switch control user navigating a form may take 30 to 60 seconds to complete interactions that a mouse user handles in under five. An architecture that treats this as a timeout scenario rather than a legitimate usage pattern is an architecture that excludes this user entirely. Event-driven designs, CQRS patterns, and saga patterns that accommodate extended interaction timescales are not just engineering elegance — they are what allow real users to complete real tasks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API contracts need to be designed for multi-modal interfaces from day one:&lt;/strong&gt; This is a point that backend engineers sometimes resist, feeling that accessibility is a frontend concern. It is not. An image resource endpoint that returns URLs without alt text forces frontend developers to invent accessibility metadata that should live in the data layer. An error response that returns generic 500 status codes with no semantic content makes it impossible to build screen-reader-friendly error messages. An API that lacks hypermedia controls forces assistive technologies to navigate complex workflows without any structural guidance. Backend API design choices have direct accessibility implications, and those choices need to be made deliberately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fallback mechanisms are primary paths, not error handling:&lt;/strong&gt; Accessible systems are resilient systems. If a WebSocket connection fails, keyboard navigation cannot become broken. If a JavaScript error disrupts dynamic content updates, there needs to be a fallback mechanism for content announcements. Progressive enhancement — the practice of building a functional core experience that works without advanced features, then layering enhancements on top — is not just a best practice for low-bandwidth environments. It is an accessibility architectural pattern.&lt;/p&gt;

&lt;h3&gt;
  
  
  Embedding Accessibility Across the Development Lifecycle
&lt;/h3&gt;

&lt;p&gt;One of the more powerful shifts in accessibility-first engineering is what happens when accessibility requirements are treated as first-class functional requirements from the very beginning of a project, rather than as a quality checklist at the end.&lt;/p&gt;

&lt;p&gt;In the requirements phase, this means writing accessibility specifications with the same rigor as performance requirements. What WCAG conformance level is the target? What assistive technologies need to be tested against? What are the performance budgets for accessibility-critical interactions? What error messaging requirements exist for cognitive accessibility? These questions should have answers before a line of code is written.&lt;/p&gt;

&lt;p&gt;In the design phase, it means architecture reviews that explicitly evaluate accessibility implications. Decisions about synchronous versus asynchronous processing, caching strategies, API contract design, and state management patterns all have accessibility consequences. Those consequences need to be part of the design conversation.&lt;/p&gt;

&lt;p&gt;In the development phase, it means accessibility patterns in coding standards, not accessibility reviews in code review. There is a meaningful difference between a team that checks for accessibility problems after code is written and a team where accessible patterns are the default from which developers start.&lt;/p&gt;

&lt;p&gt;In the testing phase, it means automated accessibility testing running in CI/CD pipelines alongside unit and integration tests, and manual testing with actual assistive technologies treated as a standard activity rather than a specialized audit. It also means load testing scenarios that include assistive technology usage patterns, which often produce meaningfully different request profiles than standard interactions.&lt;/p&gt;

&lt;p&gt;In the monitoring phase — and this is where accessibility-first thinking often reveals gaps — it means measuring “accessibility uptime” as a distinct metric from general system uptime. A system can be fully operational for mouse users while being completely broken for keyboard-only users because of a JavaScript error that disrupts focus management. Standard uptime monitoring will not catch this. Intentional accessibility observability will.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Engineering Culture Dimension
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Technical practices can be documented in a style guide. Culture is harder.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One of the most common patterns in organizations that struggle with accessibility is that it is owned by a single person or a small team who bear the entire cognitive and organizational load of making inaccessible systems accessible. These people are often talented, passionate, and chronically overwhelmed. They become bottlenecks. They review finished work looking for problems. They write long reports that get triaged against other priorities. And eventually, they burn out.&lt;/p&gt;

&lt;p&gt;Accessibility-first engineering requires distributing this ownership. It requires that every engineer building a feature understands enough about accessibility to make good decisions in their day-to-day work. It requires that product managers include accessibility requirements in acceptance criteria. It requires that designers understand the accessibility implications of their patterns. It requires that engineering leadership sets clear expectations and measures results.&lt;/p&gt;

&lt;p&gt;This is not a simple transformation. It requires investment in education, in tooling, in processes that make the right choices easy and the wrong choices visible. But it is the only version of accessibility work that actually scales. A single team auditing the output of hundreds of engineers will always be fighting a losing battle. A culture where accessibility is everyone’s responsibility — supported by good tooling and clear standards — is the only thing that produces consistently accessible software.&lt;/p&gt;

&lt;p&gt;Staff engineers and technical leads carry particular responsibility here. The architectural decisions made at the senior level — about system design, about API contracts, about component libraries, about testing infrastructure — either make accessibility easy or make it hard for every engineer downstream. Technical leadership that treats accessibility as a first-class concern creates the conditions for accessible software to be the path of least resistance. Technical leadership that does not creates the conditions for accessibility debt to accumulate indefinitely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Closing: The Standard We Should Hold Ourselves To
&lt;/h3&gt;

&lt;p&gt;There is a version of this conversation that frames accessibility as a nice-to-have, a differentiator, a business opportunity. All of those things are true, and none of them are the right starting point.&lt;/p&gt;

&lt;p&gt;The right starting point is this: millions of people cannot fully use software that has become essential to participating in modern society. Healthcare, employment, education, civic participation — all of it increasingly mediated through digital systems that were built without them in mind. That is a failure. It is a failure of craft, of leadership, and of professional ethics.&lt;/p&gt;

&lt;p&gt;Accessibility-first software engineering is the practice of taking that failure seriously enough to actually change how we build things. Not by adding an accessibility review at the end of the process, but by designing systems from the ground up that work for everyone. Not by treating disabled users as an edge case, but by recognizing that designing for the full spectrum of human capability produces better software for all users.&lt;/p&gt;

&lt;p&gt;The curb cut is still the right metaphor. The features that make software accessible to people with disabilities — keyboard navigability, semantic structure, clear error handling, consistent state exposure, meaningful labels — make software better for everyone. They make it more robust, more maintainable, more testable, and more resilient. Accessibility is not a constraint on good engineering. It is an expression of it.&lt;/p&gt;

&lt;p&gt;The question for every engineering team is not whether to take accessibility seriously. It is whether to take it seriously before you ship or after, and whether you want to pay for it once or many times over.&lt;/p&gt;

</description>
      <category>softwaredesign</category>
      <category>a11y</category>
      <category>engineering</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Vector Search and Queryable Encryption in .NET: Engineering Secure AI Systems at Scale</title>
      <dc:creator>Ali Suleyman TOPUZ</dc:creator>
      <pubDate>Sat, 21 Mar 2026 18:46:33 +0000</pubDate>
      <link>https://forem.com/topuzas/vector-search-and-queryable-encryption-in-net-engineering-secure-ai-systems-at-scale-2ocg</link>
      <guid>https://forem.com/topuzas/vector-search-and-queryable-encryption-in-net-engineering-secure-ai-systems-at-scale-2ocg</guid>
      <description>&lt;p&gt;A comprehensive technical deep-dive for .NET architects and senior engineers on building production-grade vector search systems with encryption-in-use. Explores the intersection of semantic search, LLM embeddings, and privacy-preserving computation in enterprise environments where regulatory compliance and performance cannot be compromised.&lt;/p&gt;

&lt;h3&gt;
  
  
  Executive Summary
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Contextual Importance: The Convergence of Three Inflection Points
&lt;/h4&gt;

&lt;p&gt;The enterprise software landscape is experiencing a profound transformation driven by three simultaneous inflection points. First, the explosive growth in &lt;strong&gt;unstructured data&lt;/strong&gt;  — high-dimensional vector representations derived from text, images, and audio. Second, the transition of &lt;strong&gt;privacy-preserving AI&lt;/strong&gt; from academic curiosity to regulatory mandate (GDPR, HIPAA, and the EU AI Act). Third, the strategic challenge of &lt;strong&gt;Vector Search and Encryption&lt;/strong&gt; : the need to perform mathematical similarity operations on data that must remain protected.&lt;/p&gt;

&lt;h3&gt;
  
  
  Target Technologists: Who Needs This Knowledge Now
&lt;/h3&gt;

&lt;p&gt;This deep-dive addresses .NET architects building LLM-powered enterprise systems and Security Engineers tasked with auditing AI infrastructure. We move beyond theoretical “Hello World” tutorials to explore how to ship reliable, compliant, and high-performance vector systems at scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  Core Architectural Components
&lt;/h3&gt;

&lt;p&gt;Building a production-grade system requires orchestrating specialized components that handle high-dimensional math and cryptographic transformations.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Component Topology
&lt;/h4&gt;

&lt;h4&gt;
  
  
  Vector Embedding Service Layer
&lt;/h4&gt;

&lt;p&gt;This layer converts raw data into vectors (e.g., 1536-dimensional floats for OpenAI’s text-embedding-3-small).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Isolation&lt;/strong&gt; : Use &lt;strong&gt;ASP.NET Core Minimal APIs&lt;/strong&gt; with System.Threading.RateLimiting to protect against upstream LLM latency and costs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Handling&lt;/strong&gt; : Use ReadOnlyMemory or Span to ensure zero-copy semantics, avoiding expensive array allocations in the hot path.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Vector Store Layer: The Persistence Conundrum
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL + pgvector&lt;/strong&gt; : Best for teams needing ACID compliance and relational joins. Integrated via Npgsql.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Qdrant&lt;/strong&gt; : A purpose-built Rust-based store with excellent gRPC support for .NET. Ideal for sub-50ms latencies and complex metadata filtering.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Hands-on Vector Search Logic in .NET
&lt;/h3&gt;

&lt;p&gt;We will implement a production-ready vector search service using  &lt;strong&gt;.NET 9&lt;/strong&gt; and &lt;strong&gt;C# 13&lt;/strong&gt; features.&lt;/p&gt;

&lt;h4&gt;
  
  
  Foundation: Domain Models
&lt;/h4&gt;

&lt;p&gt;We start by defining type-safe models that separate the vector data from its business metadata.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;VectorDocument&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="n"&gt;ReadOnlyMemory&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Vector&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="n"&gt;VectorMetadata&lt;/span&gt; &lt;span class="n"&gt;Metadata&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;init&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="n"&gt;EncryptionScheme&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;init&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;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;record&lt;/span&gt; &lt;span class="nc"&gt;VectorMetadata&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;ModelVersion&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  The Implementation: Encrypted Search Service
&lt;/h4&gt;

&lt;p&gt;The following implementation demonstrates a “Player-Coach” approach: a service that handles both the embedding generation and the secure search execution.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SecureVectorSearchService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;IVectorEmbeddingService&lt;/span&gt; &lt;span class="n"&gt;embeddingService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;IVectorStore&lt;/span&gt; &lt;span class="n"&gt;vectorStore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;IVectorEncryptionService&lt;/span&gt; &lt;span class="n"&gt;encryptionService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;SecureVectorSearchService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ISecureVectorSearchService&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IReadOnlyList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;VectorSearchResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;SearchProtectedAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;plainTextQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;CancellationToken&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 1. Generate Embedding&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rawVector&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;embeddingService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GenerateEmbeddingAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;plainTextQuery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// 2. Apply Search-Optimized Encryption (e.g., Order-Preserving or Homomorphic)&lt;/span&gt;
        &lt;span class="c1"&gt;// This allows the DB to perform distance calculations without seeing the raw values.&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;searchToken&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;encryptionService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EncryptForSearch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rawVector&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;VectorSearchRequest&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;QueryVector&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;searchToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Pass the encrypted token to the store&lt;/span&gt;
            &lt;span class="n"&gt;TopK&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;UseEncryption&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="c1"&gt;// 3. Execute Search with Observability&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sw&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Stopwatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartNew&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; 
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;vectorStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SearchAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;finally&lt;/span&gt; 
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Vector search completed in {Elapsed}ms"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Elapsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalMilliseconds&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Mathematical Accuracy in Distance Calculation
&lt;/h4&gt;

&lt;p&gt;When implementing custom similarity logic (e.g., for in-memory caching), use hardware acceleration via System.Runtime.Intrinsics.&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.amazonaws.com%2Fuploads%2Farticles%2Fpsh8f0uvxebhia7shpkz.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.amazonaws.com%2Fuploads%2Farticles%2Fpsh8f0uvxebhia7shpkz.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In .NET 9, we optimize this using &lt;strong&gt;Hardware Intrinsics (SIMD)&lt;/strong&gt;. Instead of a standard for loop, use System.Numerics.Tensors (or TensorPrimitives) to process multiple floating-point operations in a single CPU clock cycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// High-performance SIMD-optimized dot product in .NET 9&lt;/span&gt;
&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;similarity&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TensorPrimitives&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vectorA&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vectorB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Observability and Scaling
&lt;/h3&gt;

&lt;p&gt;In production, you cannot manage what you do not measure.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vector Drift&lt;/strong&gt; : Monitor the statistical distribution of your embeddings. If the average distance between new embeddings and your baseline increases, your model may need retraining.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recall vs. Latency&lt;/strong&gt; : Use &lt;strong&gt;OpenTelemetry&lt;/strong&gt; to track the trade-off between HNSW ef_search parameters and search accuracy.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Metrics, Tooling and Target
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;| Metric | Tooling | Target (P99) |
|---------------------|------------------------|--------------|
| Embedding Latency | Azure Monitor | &amp;lt; 200ms |
| Vector Index Search | Prometheus / Grafana | &amp;lt; 50ms |
| Decryption Overhead | Custom DotNetCounters | &amp;lt; 5ms |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Summary of Execution
&lt;/h3&gt;

&lt;p&gt;We have moved from the high-level need for secure AI to a concrete .NET implementation. By leveraging ReadOnlyMemory for performance and isolating the encryption logic, we build a system that is both fast and compliant.&lt;/p&gt;

</description>
      <category>search</category>
      <category>encryption</category>
      <category>enterprisearchitectu</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>Semantic Kernel and AI Agent Architecture: Orchestrating Enterprise LLMs in .NET 9</title>
      <dc:creator>Ali Suleyman TOPUZ</dc:creator>
      <pubDate>Sat, 21 Mar 2026 18:45:49 +0000</pubDate>
      <link>https://forem.com/topuzas/semantic-kernel-and-ai-agent-architecture-orchestrating-enterprise-llms-in-net-9-4n4p</link>
      <guid>https://forem.com/topuzas/semantic-kernel-and-ai-agent-architecture-orchestrating-enterprise-llms-in-net-9-4n4p</guid>
      <description>&lt;p&gt;A staff engineer’s deep-dive into Microsoft’s Semantic Kernel framework for building production-grade AI agents. Learn why enterprise LLM integration fails and how orchestration frameworks solve memory, composability, and operational challenges.&lt;/p&gt;

&lt;h3&gt;
  
  
  Executive Summary
&lt;/h3&gt;

&lt;p&gt;For Senior Software Engineers, Semantic Kernel (SK) represents a paradigm shift. It doesn’t just simplify LLM integration — it fundamentally restructures application boundaries, state management, and workflow orchestration when non-deterministic AI components become first-class citizens in our architecture. In .NET 9, this is further solidified by the Microsoft.Extensions.AI (MEAI) ecosystem, allowing for a decoupled, vendor-agnostic AI stack.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Core Challenge: The Stateless Black-Box Dilemma
&lt;/h3&gt;

&lt;p&gt;Traditional APIs are predictable; LLMs are probabilistic. They are &lt;strong&gt;Stateless&lt;/strong&gt; , &lt;strong&gt;Non-deterministic&lt;/strong&gt; , and &lt;strong&gt;Context-Limited&lt;/strong&gt;. Bridging this gap requires an orchestrator to manage context, enforce schemas, and provide observability.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Conceptual Overview of Semantic Kernel
&lt;/h3&gt;

&lt;h4&gt;
  
  
  In the modern .NET 9 ecosystem, the architecture is split into two layers:
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Foundational Layer (&lt;/strong&gt;&lt;strong&gt;Microsoft.Extensions.AI):&lt;/strong&gt; Provides the standard interfaces like IChatClient.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Orchestration Layer (Semantic Kernel):&lt;/strong&gt; Uses those interfaces to manage &lt;strong&gt;Plugins&lt;/strong&gt; (the hands), &lt;strong&gt;Planners&lt;/strong&gt; (the brain), and &lt;strong&gt;Filters&lt;/strong&gt; (the guardrails).&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  3. Implementation Deep Dive in .NET 9
&lt;/h3&gt;

&lt;p&gt;As a “Player-Coach,” I don’t just talk architecture; I write the “Golden Path” code. Here is how we implement a production-grade Kernel setup in .NET 9&lt;/p&gt;

&lt;h4&gt;
  
  
  3.1 Bootstrap and Configuration
&lt;/h4&gt;

&lt;p&gt;We leverage the new .NET 9 abstractions to ensure our kernel is decoupled from the specific model provider.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.SemanticKernel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.AI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Microsoft.Extensions.DependencyInjection&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;Azure.AI.OpenAI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AIOrchestrationExtensions&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="nf"&gt;AddEnterpriseAIServices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="n"&gt;IServiceCollection&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IConfiguration&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 1. Setup the Foundational IChatClient (standard in .NET 9)&lt;/span&gt;
        &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddChatClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseFunctionCalling&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// Enables the model to use tools&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;UseOpenTelemetry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// Native distributed tracing&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AzureOpenAIChatClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI:Endpoint"&lt;/span&gt;&lt;span class="p"&gt;]!),&lt;/span&gt; 
                &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;AzureKeyCredential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI:ApiKey"&lt;/span&gt;&lt;span class="p"&gt;]!),&lt;/span&gt;
                &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"AzureOpenAI:ModelId"&lt;/span&gt;&lt;span class="p"&gt;]!)));&lt;/span&gt;

        &lt;span class="c1"&gt;// 2. Build the Semantic Kernel using the Chat Client above&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateBuilder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddSingleton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BuildServiceProvider&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IChatClient&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;());&lt;/span&gt;

        &lt;span class="c1"&gt;// 3. Register Business Logic Plugins&lt;/span&gt;
        &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Plugins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddFromType&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;InventoryPlugin&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
        &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Plugins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddFromType&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;VendorContractPlugin&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;

        &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddTransient&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Kernel&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;services&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3.2 Building a Native Plugin
&lt;/h4&gt;

&lt;p&gt;Native plugins are deterministic C# methods. The [Description] attribute is crucial; it acts as the "API Documentation" for the LLM.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InventoryPlugin&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;KernelFunction&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;Description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Returns current stock levels for a product SKU."&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetStockLevelAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// High-perf DB or gRPC call logic here&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FromResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3.3 Auto-Invocation Loop
&lt;/h4&gt;

&lt;p&gt;In .NET 9, we don’t manually call tools. We let the kernel “think” and call them automatically until the goal is met.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ProcessAgentRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Kernel&lt;/span&gt; &lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;userPrompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// AutoInvokeKernelFunctions handles the "Reasoning Loop"&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;OpenAIPromptExecutionSettings&lt;/span&gt; 
    &lt;span class="p"&gt;{&lt;/span&gt; 
        &lt;span class="n"&gt;ToolCallBehavior&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ToolCallBehavior&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AutoInvokeKernelFunctions&lt;/span&gt; 
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InvokePromptAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userPrompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WriteLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&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;h3&gt;
  
  
  4. Observability and Resilience
&lt;/h3&gt;

&lt;p&gt;For a Senior Architect, &lt;strong&gt;Observability is non-negotiable.&lt;/strong&gt;  .NET 9’s AI stack is built on ActivitySource, making OpenTelemetry integration native.&lt;/p&gt;

&lt;h4&gt;
  
  
  The “Guardrail” Filter
&lt;/h4&gt;

&lt;p&gt;We use IFunctionInvocationFilter to intercept calls before they execute. This is where we check permissions or cost limits.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SafetyFilter&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IFunctionInvocationFilter&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;OnFunctionInvocationAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FunctionInvocationContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FunctionInvocationContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Pre-call: Check if the SKU being requested is authorized&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"GetStockLevelAsync"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* Logic */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Execute the actual function&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;h3&gt;
  
  
  5. Practical Use Case: Supply Chain Assistant
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario:&lt;/strong&gt; Automating stock exceptions.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ingestion:&lt;/strong&gt; The agent sees low stock via InventoryPlugin.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RAG:&lt;/strong&gt; It queries the &lt;strong&gt;Vector Store&lt;/strong&gt; (Vendor Contracts) for lead times.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Action:&lt;/strong&gt; If stock is critical, it drafts an email automatically.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;HandleStockShortageAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Kernel&lt;/span&gt; &lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;@"You are a Supply Chain Assistant. 
                      Check stock for {{$sku}}. 
                      Search contracts for lead times. 
                      If stock &amp;lt; 10 and lead time &amp;gt; 5 days, draft a restock email."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InvokePromptAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&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="s"&gt;"sku"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="n"&gt;_logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LogInformation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Agent Output: {Result}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&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;h3&gt;
  
  
  6. Distributed State &amp;amp; Long-term Memory
&lt;/h3&gt;

&lt;p&gt;In production, AI agents often run on stateless infrastructure (like Azure Functions or Container Apps). However, a “human-like” assistant must remember preferences from five minutes ago and technical specs from a 500-page manual. Semantic Kernel handles this through &lt;strong&gt;Chat History&lt;/strong&gt; and &lt;strong&gt;Vector Stores&lt;/strong&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  6.1 Chat History: Managing the Conversation Context
&lt;/h4&gt;

&lt;p&gt;LLMs do not “remember” sessions. We must pass the entire conversation back to them with every request. In .NET 9, we architect this using a persistent IChatHistory store.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;ExecuteStatefulConversationAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Kernel&lt;/span&gt; &lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 1. Load conversation history from a persistent store (e.g., Redis or CosmosDB)&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_sessionRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetHistoryAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Append the new user input&lt;/span&gt;
    &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddUserMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 3. Invoke the Chat Completion service with history&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;chatService&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetRequiredService&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;IChatCompletionService&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;chatService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetChatMessageContentAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 4. Update the store with the assistant's response&lt;/span&gt;
    &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddAssistantMessage&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;Content&lt;/span&gt;&lt;span class="p"&gt;!);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_sessionRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SaveHistoryAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;history&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;h4&gt;
  
  
  6.2 Vector Stores: The “Corporate Brain” (RAG)
&lt;/h4&gt;

&lt;p&gt;.NET 9 introduces a standardized &lt;strong&gt;Vector Store abstraction&lt;/strong&gt;. This allows the Agent to perform Retrieval Augmented Generation (RAG) by searching across vectorized corporate data.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Architectural Advantage:&lt;/strong&gt; By using the IVectorStore interface, your code remains decoupled from the specific database provider (e.g., Azure AI Search, Pinecone, or Milvus).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metadata Filtering:&lt;/strong&gt; A staff-level implementation doesn’t just search for “text similarity.” It uses metadata (e.g., DepartmentId, SecurityLevel) to ensure the Agent only retrieves information the user is authorized to see.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Staff Engineer Note:&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;Always implement&lt;/em&gt; &lt;strong&gt;&lt;em&gt;Semantic Caching&lt;/em&gt;&lt;/strong&gt; &lt;em&gt;. Before sending a query to the LLM, check if a similar question has been answered recently in your Vector Store to save on token costs and reduce latency.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  7. Operational Excellence: Token Economics and Advanced Guardrails
&lt;/h3&gt;

&lt;p&gt;In production, the “cool factor” of AI fades quickly if the cloud bill spikes. As architects, we must treat LLM tokens like any other expensive resource (e.g., IOPS or egress).&lt;/p&gt;

&lt;h4&gt;
  
  
  7.1 Token Management and Semantic Caching
&lt;/h4&gt;

&lt;p&gt;Every word sent to the LLM costs money and increases latency. To optimize this, we implement a &lt;strong&gt;Semantic Cache&lt;/strong&gt;. Before the Kernel hits the LLM, it checks a Vector Store to see if a similar question was answered recently.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;GetOptimizedResponseAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Kernel&lt;/span&gt; &lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;userPrompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 1. Search Vector Cache for a 'similar enough' previous question&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cachedResponse&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_vectorCache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetSimilarResultAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userPrompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.95&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cachedResponse&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cachedResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. If no cache hit, proceed to LLM&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InvokePromptAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userPrompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 3. Update cache for future hits&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_vectorCache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StoreResultAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userPrompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ToString&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;h4&gt;
  
  
  7.2 Advanced Guardrails: The “Planner Validator”
&lt;/h4&gt;

&lt;p&gt;In Section 4, we discussed simple filters. For high-stakes environments, we need a &lt;strong&gt;Plan Validation Step&lt;/strong&gt;. If an agent generates a plan to “Delete User Account,” a deterministic layer must intercept it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PlanValidationFilter&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IAutoFunctionInvocationFilter&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;OnAutoFunctionInvocationAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AutoFunctionInvocationContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AutoFunctionInvocationContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Staff-level check: Is the agent trying to call a restricted tool?&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Delete"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StringComparison&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrdinalIgnoreCase&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(!&lt;/span&gt;&lt;span class="nf"&gt;IsUserAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Kernel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Arguments&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"userId"&lt;/span&gt;&lt;span class="p"&gt;]?.&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;UnauthorizedAccessException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Agent attempted unauthorized destructive action."&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;await&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  7.3 Governance: Rate Limiting and Circuit Breakers
&lt;/h4&gt;

&lt;p&gt;LLM APIs can be flaky or slow. By wrapping our IChatClient (configured in Section 3.1) with standard &lt;strong&gt;Polly&lt;/strong&gt; policies, we ensure our .NET 9 application doesn't hang when OpenAI/Azure is under load.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Retry Pattern:&lt;/strong&gt; For 429 Too Many Requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Circuit Breaker:&lt;/strong&gt; To stop calling a degraded model and fallback to a smaller, cheaper one (e.g., GPT-4o to GPT-4o-mini).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  8. Testing and Evaluation (The “Staff” Reality Check)
&lt;/h3&gt;

&lt;p&gt;Unlike traditional unit tests, AI testing is probabilistic. We use &lt;strong&gt;LLM-assisted Evaluation&lt;/strong&gt; (LLM-as-a-judge).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Input:&lt;/strong&gt; User Question + Agent Answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validator:&lt;/strong&gt; A separate, highly-capable model (like GPT-4o) evaluates the answer based on a rubric (Accuracy, Tone, Grounding).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Result:&lt;/strong&gt; A numeric score for CI/CD pipelines.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Semantic Kernel in .NET 9 is more than a library; it is the implementation of a &lt;strong&gt;Reliable AI Distributed System&lt;/strong&gt;. By combining the new IChatClient abstractions, IVectorStore memory, and IFunctionInvocationFilters, we move AI from a "chat box" to a mission-critical enterprise asset.&lt;/p&gt;

</description>
      <category>semantickernel</category>
      <category>agents</category>
      <category>designsystems</category>
      <category>llmorchestration</category>
    </item>
  </channel>
</rss>
