<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.2">Jekyll</generator><link href="https://codertectura.com//atom.xml" rel="self" type="application/atom+xml" /><link href="https://codertectura.com//" rel="alternate" type="text/html" /><updated>2023-12-24T11:35:08+00:00</updated><id>https://codertectura.com//atom.xml</id><title type="html">CODERTECTURA</title><subtitle></subtitle><author><name>Rodrigo Liberoff</name><email>rliberoff@outlook.com</email></author><entry><title type="html">¡Planificando para el éxito!</title><link href="https://codertectura.com//posts/netcoreconf-2023-madrid-planificando-para-el-exito" rel="alternate" type="text/html" title="¡Planificando para el éxito!" /><published>2023-11-20T00:00:00+00:00</published><updated>2023-11-20T00:00:00+00:00</updated><id>https://codertectura.com//posts/netcoreconf-2023-madrid-planificando-para-el-exito</id><content type="html" xml:base="https://codertectura.com//posts/netcoreconf-2023-madrid-planificando-para-el-exito"><![CDATA[<p>¡Pues ya tenemos publicado en <a href="https://youtu.be/oE1NIXiq-hE?si=t7b_jr948XQGu-bi" target="_blank">YouTube</a> la sesión que di <a href="/posts/nos-vemos-en-la-net-core-conf-2023-madrid">hace unas semanas</a> en la <a href="https://netcoreconf.com/" target="_blank">Netcoreconf 2023</a> de Madrid junto con mi complice habitual <a href="https://www.linkedin.com/in/borja-piris/" target="_blank">Borja Piris de Castro</a>.</p>

<p>En esta charla, estuve dando una introducción técnica a una de las abstracciones más esotéricas dentro del Semantic Kernel conocidas como los Planificadores, los cuales no son más que plugins que trae por defecto la librería y que sirven principalmente como orquestadores que eligen y ejecutan de entre toda la colección de funciones que tenga cargada el <em>kernel</em> aquellas que permitirían cumplir con un objetivo específico.</p>

<div class="responsive-embed responsive-embed-16by9 image-border" style="width:70%">
    <iframe src="https://www.youtube.com/embed/oE1NIXiq-hE?si=2O-Nsjjz7LS-POmw" frameborder="0" title="¡Planificando para el éxito! Usando los Planners de Semantic Kernel para realizar metas." allow="" allowfullscreen="">        
    </iframe>
</div>

<p>Los planificadores son realmente muy útiles en aquellos escenarios donde la colección de funciones disponibles es tán amplia que, para los desarrolladores de un <em>plugin</em> o un Copilot, sería imposible determinar todas las posibles combinatorias de los flujos que se necesitarían ejecutar para satisfacer todas las posibles permutaciones de las solicitudes de los usuarios.</p>

<p>En esos casos, la solución que escala mejor es aquella en la que el <em>kernel</em> pudiera aprender cómo combinar las funciones que tiene disponibles automáticamente sobre la marcha. Aquí es donde entran los planificadores, los cuales son en sí mismos funciones que toman la solicitud de un usuario (el objetivo) y devuelve un plan sobre cómo y qué funciones ejecutar para obtener un resultado para esa la solicitud. Los planificadores consiguen hacer esto haciendo uso de la Inteligencia Artificial para mezclar y combinar los <em>plugins</em> y sus funciones registradas en el <em>kernel</em>, recombinándolas en una serie de pasos que completan el objetivo solicitado. Y eso es algo muy poderos, porque a través de los planificadores será posble crear y utilizar funciones atómicas que podrían combinarse de maneras en las que sus desarrolladores quizás no hayan jamás imaginado.</p>

<p>Para .NET existen actualmente cuatro planificadores:</p>

<ul>
  <li>Action Planner</li>
  <li>Sequential Planner</li>
  <li>Stepwise Planner</li>
  <li>Custom Planner</li>
</ul>

<p>En la hoja de ruta del equipo de Semantic Kernel está pautado ir agregado planificadores más sofisticados con cada nueva versión:</p>

<p>A continuación te mostraré como funcionan cada uno de éstos, y los mejores escenarios para utilizarlos. Recuerda que todo el código fuente lo tienes disponible en mi <a href="https://github.com/rliberoff/.NET-Core-Conf-2023" target="_blank">GitHub aquí</a>.</p>

<p>También al final de este artículo encontrarás las <em>slides</em> completas de la sesión.</p>

<h3 id="action-planner">Action Planner</h3>

<p>El «Action Planner» opera identificando la función más relevante de las funciones registradas en el <em>kernel</em> que podría servir para logra el objetivo del usuario. Lo que lo diferencia de otros planificadores es que solo elige una única función a ejecutar.</p>

<p>Puede parecer un poco raro que sea útil algo que sólo considera una única función, sin embargo resulta que este planificador es útil en escenarios en los que ya se han definido otros planificadores de orden superior que logran la intención u objetivo del usuario y simplemente necesita un mecanismo para elegir el correcto. Esto es interesante pues nada impide que tengamos muchos planificadores instanciados y que relamente lo que necesitemos es elegir de entre éstos el que debemos ejecutar. Esto hace que el «Action Planner» sea muy eficiente y sea utilizado para escenarios de baja latencia.</p>

<p>El «Action Planner» funciona implementando un patrón de detección de intenciones, que identifica la intención de un usuario para el objetivo que desea cumplir, y la compara con las funciones disponibles en el <em>kernel</em>. Una vez que encuentra la función que más se alinea con el objetivo del usuario, utiliza Inteligencia Articial para completar los parámetros de entrada necesarios.</p>

<p>De la sesión en la Netcoreconf, el código que mostré para este planificador fue el siguiente:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">static</span> <span class="k">void</span> <span class="nf">InitKernel</span><span class="p">(</span><span class="n">IKernel</span> <span class="n">kernel</span><span class="p">,</span> <span class="n">IOptions</span><span class="p">&lt;</span><span class="n">BingOptions</span><span class="p">&gt;</span> <span class="n">bingOptions</span><span class="p">,</span> <span class="n">IOptions</span><span class="p">&lt;</span><span class="n">SmtpClientOptions</span><span class="p">&gt;</span> <span class="n">smptOptions</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">kernel</span><span class="p">.</span><span class="nf">ImportSemanticFunctionsFromDirectory</span><span class="p">(</span><span class="n">PluginsDirectory</span><span class="p">,</span> <span class="s">@"TextPlugin"</span><span class="p">,</span> <span class="s">@"MealsPlugin"</span><span class="p">);</span>

    <span class="n">kernel</span><span class="p">.</span><span class="nf">ImportFunctions</span><span class="p">(</span><span class="k">new</span> <span class="nf">TextPlugin</span><span class="p">(),</span> <span class="k">nameof</span><span class="p">(</span><span class="n">TextPlugin</span><span class="p">));</span>
    <span class="n">kernel</span><span class="p">.</span><span class="nf">ImportFunctions</span><span class="p">(</span><span class="k">new</span> <span class="nf">WebSearchEnginePlugin</span><span class="p">(</span><span class="k">new</span> <span class="nf">BingConnector</span><span class="p">(</span><span class="n">bingOptions</span><span class="p">.</span><span class="n">Value</span><span class="p">.</span><span class="n">Key</span><span class="p">)),</span> <span class="k">nameof</span><span class="p">(</span><span class="n">WebSearchEnginePlugin</span><span class="p">));</span>
    <span class="n">kernel</span><span class="p">.</span><span class="nf">ImportFunctions</span><span class="p">(</span><span class="k">new</span> <span class="nf">SendEmailPlugin</span><span class="p">(</span><span class="n">smptOptions</span><span class="p">),</span> <span class="k">nameof</span><span class="p">(</span><span class="n">SendEmailPlugin</span><span class="p">));</span>
<span class="p">}</span>
<span class="p">...</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">IActionResult</span><span class="p">&gt;</span> <span class="nf">ActionPlannerDemoAsync</span><span class="p">(</span><span class="n">PlannerRequest</span> <span class="n">request</span><span class="p">,</span> <span class="n">CancellationToken</span> <span class="n">cancellationToken</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">var</span> <span class="n">actionPlan</span> <span class="p">=</span> <span class="k">await</span> <span class="k">new</span> <span class="nf">ActionPlanner</span><span class="p">(</span><span class="n">kernel</span><span class="p">).</span><span class="nf">CreatePlanAsync</span><span class="p">(</span><span class="n">request</span><span class="p">.</span><span class="n">Goal</span><span class="p">,</span> <span class="n">cancellationToken</span><span class="p">);</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">actionPlan</span><span class="p">.</span><span class="n">Steps</span><span class="p">.</span><span class="n">Count</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="nf">BadRequest</span><span class="p">(</span><span class="k">new</span> <span class="nf">PlannerResponse</span><span class="p">()</span>
        <span class="p">{</span>
            <span class="n">Output</span> <span class="p">=</span> <span class="s">@"Could not create a plan. Check that the goal is supported by the planner, configured plug-ins, and functions!"</span><span class="p">,</span>
            <span class="n">Plan</span> <span class="p">=</span> <span class="n">actionPlan</span><span class="p">.</span><span class="nf">ToJson</span><span class="p">(</span><span class="k">true</span><span class="p">),</span>
        <span class="p">});</span>
    <span class="p">}</span>

    <span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="n">kernel</span><span class="p">.</span><span class="nf">RunAsync</span><span class="p">(</span><span class="n">cancellationToken</span><span class="p">,</span> <span class="n">actionPlan</span><span class="p">);</span>

    <span class="k">return</span> <span class="nf">Ok</span><span class="p">(</span><span class="k">new</span> <span class="nf">PlannerResponse</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="n">Output</span> <span class="p">=</span> <span class="n">result</span><span class="p">.</span><span class="n">GetValue</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;(),</span>
        <span class="n">Plan</span> <span class="p">=</span> <span class="n">actionPlan</span><span class="p">.</span><span class="nf">ToJson</span><span class="p">(</span><span class="k">true</span><span class="p">),</span>
    <span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Del código anterior, lo que hacemos es inicializar un <em>kernel</em> con una serie de <em>plugins</em> (y sus respectivas funciones). Seguidamente contamos con una acción de un controllador que recibe el objetivo del usuario (<em>goal</em>) e instancia un «Action Planner» el cual recibe como parámetro el <em>kernel</em> para poder buscar entre la colección de funciones la que mejor se adecúe a la consecuón del objetivo del usuario.</p>

<p>Al trabajar con ciertos planificadores, es importante determinar si se han encontado pasos a ejecutar. En caso contrario, lo más conveniente es devolver un mensaje al usuario para poder informar de la imposibilidad de poder satisfacer su objetivo. En el caso del «Action Planner» esto puede pasar por dos razones:</p>

<ol>
  <li>El objetivo del usuario involucra dos acciones, por ejemplo “<em>crear un texto y mandarlo por correo electrónico</em>”.</li>
  <li>Efectivamente en el <em>kernel</em> no hay cargado un plugin con funciones que identifiquen poder cumplir con el objetivo del usuario.</li>
</ol>

<h3 id="sequential-planner">Sequential Planner</h3>

<p>A diferencia del «Action Planner», el «Sequential Planner» destaca como un poderoso planificador capaz de ejecutar una serie de pasos pasando resultados de un paso al siguiente según corresponda de forma serial. Esto se convierte en una gran solución para escenarios en los que necesitas secuenciar funciones. Por ejemplo, realizar una búsqueda en la web sobre un tema específico para seguidamente necesitar obtener un resumen en texto del mismo que finalmente debe ser enviado como un correo electrónico a una o varias direcciones. Cada una de estas acciones corresponderían a funciones de diferentes <em>plugins</em> individuales que entre sí pueden parecer desconectados, pero gracias a un «Sequential Planner» y el poder de un LLM aporpiado se podría convertir en planes eficientes que permiten un flujo de datos fluido y listo para su ejecución.</p>

<p>De la sesión en la Netcoreconf, el código que mostré para este planificador fue el siguiente:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">const</span> <span class="kt">int</span> <span class="n">MaxTokens</span> <span class="p">=</span> <span class="m">2000</span><span class="p">;</span>
<span class="p">...</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">IActionResult</span><span class="p">&gt;</span> <span class="nf">SequentialPlannerDemoAsync</span><span class="p">(</span><span class="n">PlannerRequest</span> <span class="n">request</span><span class="p">,</span> <span class="n">CancellationToken</span> <span class="n">cancellationToken</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">var</span> <span class="n">sequentialPlannerConfig</span> <span class="p">=</span> <span class="k">new</span> <span class="n">SequentialPlannerConfig</span>
    <span class="p">{</span>
      <span class="n">RelevancyThreshold</span> <span class="p">=</span> <span class="m">0.6</span><span class="p">,</span>
      <span class="n">MaxTokens</span> <span class="p">=</span> <span class="n">MaxTokens</span>
    <span class="p">};</span>

    <span class="kt">var</span> <span class="n">sequentialPlan</span> <span class="p">=</span> <span class="k">await</span> <span class="k">new</span> <span class="nf">SequentialPlanner</span><span class="p">(</span><span class="n">kernel</span><span class="p">,</span> <span class="n">sequentialPlannerConfig</span><span class="p">).</span><span class="nf">CreatePlanAsync</span><span class="p">(</span><span class="n">request</span><span class="p">.</span><span class="n">Goal</span><span class="p">,</span> <span class="n">cancellationToken</span><span class="p">);</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">sequentialPlan</span><span class="p">.</span><span class="n">Steps</span><span class="p">.</span><span class="n">Count</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="nf">BadRequest</span><span class="p">(</span><span class="k">new</span> <span class="nf">PlannerResponse</span><span class="p">()</span>
        <span class="p">{</span>
            <span class="n">Output</span> <span class="p">=</span> <span class="s">@"Could not create a plan. Check that the goal is supported by the planner, configured plug-ins, and functions!"</span><span class="p">,</span>
            <span class="n">Plan</span> <span class="p">=</span> <span class="n">sequentialPlan</span><span class="p">.</span><span class="nf">ToJson</span><span class="p">(</span><span class="k">true</span><span class="p">),</span>
        <span class="p">});</span>
    <span class="p">}</span>

    <span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="n">kernel</span><span class="p">.</span><span class="nf">RunAsync</span><span class="p">(</span><span class="n">cancellationToken</span><span class="p">,</span> <span class="n">sequentialPlan</span><span class="p">);</span>

    <span class="k">return</span> <span class="nf">Ok</span><span class="p">(</span><span class="k">new</span> <span class="nf">PlannerResponse</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="n">Output</span> <span class="p">=</span> <span class="n">result</span><span class="p">.</span><span class="n">GetValue</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;(),</span>
        <span class="n">Plan</span> <span class="p">=</span> <span class="n">sequentialPlan</span><span class="p">.</span><span class="nf">ToJson</span><span class="p">(</span><span class="k">true</span><span class="p">),</span>
    <span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Como se puede apreciar, no es muy diferente del código del «Action Planner», salvo que el plan que se genera considera más de una función a ejecutar. Como en el caso del «Action Planner», debemos determinar si se han generado pasos, pues en caso contrario debemos notificar al usuario la imposibilida de poder satisfacer su objetivo. En este caso, a diferencia del «Action Planner» será exclusivamente porque el <em>kernel</em> no tiene configurado <em>plugins</em> o funciones que pudiera haber identificado como idóneas para conseguir el objetivo.</p>

<p>Por otro laso, un «Sequential Planner» tiene ciertos elementos de configuración, tales como el <code class="language-plaintext highlighter-rouge">RelevancyThreshold</code> que permite que el planificador filtre funciones irrelevantes al crear planes. Esto significa que durante la generación del plan solo se considerarán las funciones que se consideren relevantes para el objetivo determinado, lo que dará como resultado planes más centrados y eficientes. Este  filtrado puede ayudar a mejorar el rendimiento general del proceso de planificación y aumentar la probabilidad de generar planes exitosos para lograr objetivos complejos.</p>

<p>También es posible establecer que funciones deben ser excluidas por el planificador y no tomadas en cuenta a la hora de generar el plan, independientemente del umbral de relevancia que se establezca.</p>

<h3 id="stepwise-planner">Stepwise Planner</h3>

<p>El «Stepwise Planner» es un poderoso planificador basado en una arquitectura neurosimbólica denominada en sus términos en ingles como «Modular Reasoning, Knowledge and Language» (<a href="https://arxiv.org/pdf/2205.00445.pdf" target="_blank">MRKL</a>, y pronunciado <em>miracle</em> en Inglés).</p>

<p>Este planificador tiene un enfoque único que permite a los desarrolladores ejecutar planes paso a paso para lograr objetivos complejos dentro de sus aplicaciones. El «Stepwise Planner» es una excelente opción cuando tienes un escenario que requiere una selección dinámica de funciones para lidiar con solicitudes complejas de varios pasos interconectados. Este planificador puede “aprender” de sus errores mientras “explora” las funciones disponibles en el <em>kernel</em> para determinar cómo resolver un problema y conseguir el objetivo del usuario.</p>

<p>De la sesión en la Netcoreconf, el código que mostré para este planificador fue el siguiente:</p>

<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">private</span> <span class="k">const</span> <span class="kt">int</span> <span class="n">MaxTokens</span> <span class="p">=</span> <span class="m">2000</span><span class="p">;</span>
<span class="p">...</span>
<span class="k">public</span> <span class="k">async</span> <span class="n">Task</span><span class="p">&lt;</span><span class="n">IActionResult</span><span class="p">&gt;</span> <span class="nf">StepwisePlannerDemoAsync</span><span class="p">(</span><span class="n">PlannerRequest</span> <span class="n">request</span><span class="p">,</span> <span class="n">CancellationToken</span> <span class="n">cancellationToken</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">var</span> <span class="n">plannerConfig</span> <span class="p">=</span> <span class="k">new</span> <span class="n">StepwisePlannerConfig</span>
    <span class="p">{</span>
        <span class="n">MinIterationTimeMs</span> <span class="p">=</span> <span class="m">1500</span><span class="p">,</span>
        <span class="n">MaxIterations</span> <span class="p">=</span> <span class="m">5</span><span class="p">,</span>
        <span class="n">MaxTokens</span> <span class="p">=</span> <span class="n">MaxTokens</span><span class="p">,</span>
    <span class="p">};</span>

    <span class="kt">var</span> <span class="n">stepwisePlan</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">StepwisePlanner</span><span class="p">(</span><span class="n">kernel</span><span class="p">,</span> <span class="n">plannerConfig</span><span class="p">).</span><span class="nf">CreatePlan</span><span class="p">(</span><span class="n">request</span><span class="p">.</span><span class="n">Goal</span><span class="p">);</span>
    <span class="kt">var</span> <span class="n">result</span> <span class="p">=</span> <span class="k">await</span> <span class="n">kernel</span><span class="p">.</span><span class="nf">RunAsync</span><span class="p">(</span><span class="n">cancellationToken</span><span class="p">,</span> <span class="n">stepwisePlan</span><span class="p">);</span>

    <span class="k">return</span> <span class="nf">Ok</span><span class="p">(</span><span class="k">new</span> <span class="nf">PlannerResponse</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="n">Output</span> <span class="p">=</span> <span class="n">result</span><span class="p">.</span><span class="n">GetValue</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">&gt;(),</span>
        <span class="n">Plan</span> <span class="p">=</span> <span class="n">stepwisePlan</span><span class="p">.</span><span class="nf">ToJson</span><span class="p">(</span><span class="k">true</span><span class="p">),</span>
    <span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Ahora, este código de por si no nos dice mucho sin el ejemplo del objetivo que pedimos en su momento. Lo que haremos es pedirle que cree una receta vegana empleando huevos, los cuales obviamente no pueden ser empleados en este tipo de comidas al ser un producto de origen animal. Lo que va a ocurrir es que el planificador entrará en duda de cómo poder cumplir con el objetivo del usuario, por lo cual buscará a través de Bing confirmar si los huevos son utilizables en recetas veganas o no, y al determinar que no lo son, retorna un “No es Posible” como resultado. Esto lo puedes ver en las trazas de la ejecución:</p>

<figure class="align-center"><a href="https://codertectura.com//images/2023-11-20-netcoreconf-2023-madrid-planificando-para-el-exito/logs.png" target="_blank" rel="noopener" data-lity="">
            <img src="/images/2023-11-20-netcoreconf-2023-madrid-planificando-para-el-exito/logs.png" alt="" class="image-border " style="" />
        </a><figcaption>
        Haz click para ver la imagen más grande.

    </figcaption></figure>

<p>Recuerda que tienes más detalle de todo esto en el código de esta publicación en mi <a href="https://github.com/rliberoff/.NET-Core-Conf-2023" target="_blank">GitHub aquí</a>.</p>

<h2 id="dónde-está-la-mágia">¿Dónde está la mágia?</h2>

<p>Como decía antes, los planificadores no son más que <em>plugins</em> para Semantic Kernel con funciones que utilizan mensajes para un LLM (<em>Large Language Model</em>), habitualmente alguno de OpenAI, para generar un plan. Por ejemplo, el mensaje que utiliza el «Sequential Planner» lo podeos encontrar en el código fuente de Semantic Kernel dentro de un archivo <code class="language-plaintext highlighter-rouge">skprompt.txt</code> (es decir, que es una <a href="https://youtu.be/jc6H8gmXAAA?si=bnXygSteId3XR64Q" target="_blank">función semántica</a>), y parece algo así como lo siguiente:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Create an XML plan step by step, to satisfy the goal given.
To create a plan, follow these steps:
0. The plan should be as short as possible.
1. From a &lt;goal&gt; create a &lt;plan&gt; as a series of &lt;functions&gt;.
2. Before using any function in a plan, check that it is present in the most recent [AVAILABLE FUNCTIONS] list. If it is not, do not use it. Do not assume that any function that was previously defined or used in another plan or in [EXAMPLES] is automatically available or compatible with the current plan.
3. Only use functions that are required for the given goal.
4. A function has a single 'input' and a single 'output' which are both strings and not objects.
5. The 'output' from each function is automatically passed as 'input' to the subsequent &lt;function&gt;.
6. 'input' does not need to be specified if it consumes the 'output' of the previous function.
7. To save an 'output' from a &lt;function&gt;, to pass into a future &lt;function&gt;, use &lt;function.{FunctionName} ... setContextVariable: "&lt;UNIQUE_VARIABLE_KEY&gt;"/&gt;
8. To save an 'output' from a &lt;function&gt;, to return as part of a plan result, use &lt;function.{FunctionName} ... appendToResult: "RESULT__&lt;UNIQUE_RESULT_KEY&gt;"/&gt;
9. Append an "END" XML comment at the end of the plan.

[AVAILABLE FUNCTIONS]

{{$available_functions}}

[END AVAILABLE FUNCTIONS]

&lt;goal&gt;{{$input}}&lt;/goal&gt;
</code></pre></div></div>

<p>Por lo tanto, realmente lo que ocurre cuando utilizamos un planificador es que estamos instanciando un <em>plugin</em> o función, pasándole algunos parámetros especificos y recibiendo el resultado el cual no es más que el producto de sucesivas llamadas a un LLM.</p>

<p>Cada planificador es capaz de identificar que funciones necesita incluir a partir de la propiedad <code class="language-plaintext highlighter-rouge">description</code> de la función y de sus parámetros.</p>

<p>Por ejemplo, del siguiente JSON, un LLM que esté siendo utilizado por un planificador podrá saber que la función descrita sirve para “<em>crear recetas para un estilo de vida vegano</em>” y que hay dos parámetros: uno para indicar el tipo de plato a preparar (entrante, principal o postre) y otro para indicar el ingrediente principal a utilizar:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"schema"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
  </span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"A Nutrition Coach with expertise in a Vegan lifestyle who creates vegan recipes for any course dish."</span><span class="p">,</span><span class="w">
  </span><span class="nl">"models"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="w">
      </span><span class="nl">"max_tokens"</span><span class="p">:</span><span class="w"> </span><span class="mi">2000</span><span class="p">,</span><span class="w">
      </span><span class="nl">"temperature"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.9</span><span class="p">,</span><span class="w">
      </span><span class="nl">"top_p"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"presence_penalty"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"frequency_penalty"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"stop_sequences"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="s2">"[done]"</span><span class="w">
      </span><span class="p">]</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">],</span><span class="w">
  </span><span class="nl">"input"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"parameters"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="p">{</span><span class="w">
        </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"input"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"The course of the vegan dish you want the recipe to prepare. For example: Starter, Main, or Dessert."</span><span class="p">,</span><span class="w">
        </span><span class="nl">"defaultValue"</span><span class="p">:</span><span class="w"> </span><span class="s2">""</span><span class="w">
      </span><span class="p">},</span><span class="w">
      </span><span class="p">{</span><span class="w">
        </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"mainIngredient"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"The main ingredient to use in the recipe to prepare."</span><span class="p">,</span><span class="w">
        </span><span class="nl">"defaultValue"</span><span class="p">:</span><span class="w"> </span><span class="s2">"[No main ingredient]"</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">]</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Gracias a dicho campo <code class="language-plaintext highlighter-rouge">description</code> combinado con los <em>prompts</em> de los planificadores, es que se contruyen los planes tan sofisticados que son capaces de generar el «Sequential Planner» o el «Stepwise Planner».</p>

<h2 id="trucos-pros-y-contras">Trucos, pros y contras</h2>

<ul>
  <li>Ten en cuenta que usar planificadores tiene un impacto importante en el rendimiento de tus aplicaciones. Los planificadores son lentos.</li>
  <li>Puesto que los planificadores realizan varias llamadas al LLM, pueden incrementar de forma importante tu costo por consumo de estos servicios.</li>
  <li>Recuerda que esta tecnología no es discreta sino estocástica, y por lo tanto siempre existe la posibilidad de que se generen planes defectuosos. Para que el planificador sea sólido, lo mejos es  proporcionar un adecuado manejo de errores. Por ejemplo, si el planificador debe retornar un esquema con formato específico (JSON, XML, etc.) y genera un resultado incorrecto o con esquema inválido, se podría implementar una política de reintentos solicitando al planificador que “arregle” el plan.</li>
  <li>La mejor manera de mitigar un consumo exesivo es excluyendo aquellas funciones que sabemos nuestro planificador no debería tomar en cuenta.</li>
  <li>También ayuda a planificar mejor el proporcionar descripciones verbosas, completas, concretas y no ambiguas de cada función y sus parámetros de entrada. En la descripción de la función se puede especificar por ejemplo la salida de la misma.</li>
</ul>

<h2 id="para-cerrar">Para cerrar</h2>

<p>Finalmente, aquí os dejo la presentación completa de la sesión 😎</p>

<div class="responsive-embed responsive-embed-16by9 image-border" style="">
    <iframe src="https://www.slideshare.net/slideshow/embed_code/key/8fAP7L3yaRBoeL" frameborder="0" title="¡Planificando para el éxito! Usando los Planners de Semantic Kernel para realizar metas." allow="" allowfullscreen="">        
    </iframe>
</div>]]></content><author><name>Rodrigo Liberoff</name><email>rliberoff@outlook.com</email></author><category term="Eventos" /><category term="Netcoreconf" /><category term="Inteligencia Artificial" /><category term="Semantic Kernel" /><category term="Tutorial" /><summary type="html"><![CDATA[Este es el artículo técnico con el detalle de todo lo que mostré durante mi sesión para la Netcoreconf 2023 en Madrid sobre cómo usar los Planners de Semantic Kernel para poder realizar las metas solicitadas por los usuarios.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://codertectura.com//images/2023-11-20-netcoreconf-2023-madrid-planificando-para-el-exito/header.jpg" /><media:content medium="image" url="https://codertectura.com//images/2023-11-20-netcoreconf-2023-madrid-planificando-para-el-exito/header.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Estaré en la Netcoreconf 2023 de Madrid</title><link href="https://codertectura.com//posts/nos-vemos-en-la-net-core-conf-2023-madrid" rel="alternate" type="text/html" title="Estaré en la Netcoreconf 2023 de Madrid" /><published>2023-11-13T00:00:00+00:00</published><updated>2023-11-13T00:00:00+00:00</updated><id>https://codertectura.com//posts/nos-vemos-en-la-net-core-conf-2023-madrid</id><content type="html" xml:base="https://codertectura.com//posts/nos-vemos-en-la-net-core-conf-2023-madrid"><![CDATA[<p>¡Pues al final se alinearon los planetas y las estrellas y surgió la oportunidad de poder presentar una charla en la Netcoreconf 2023 de Madrid!</p>

<p>Inicialmente mi charla, que iba a dar conjuntamente con <a href="https://www.linkedin.com/in/borja-piris/" target="_blank">Borja Piris</a> no quedó entre las seleccionadas para el evento <a href="https://netcoreconf.com/" target="_blank">Netcoreconf 2023 en Madrid</a>.</p>

<p>Sin embargo, como por cosas de la vida y una serendípia enorma, se abrío la posibilidad de que si participaramos como ponentes (porque igual como asistente iba a ir 😉). Todo comenzó este lunes 13 de noviembre cuando <a href="https://twitter.com/dzapic0" target="_blank">Diego “Zapi” Zapico</a> me contacta para comentame que desde la organización del evento estaban preguntando si desde mi empresa actual (<a href="https://www.encamina.com/" target="_blank">ENCAMIA</a>) había alguien que pudiera preprar rápidamente una charla pues se había liberado uno de los <em>slots</em> de charlas. Rápidamente Zapi me lo comenta y le digo «<em>que si… ¡que claro que estoy muy interesado!</em>».</p>

<p>De inmediato le comentamos a la organización que eligieran alguna de las charlas que habíamos propuesto por <a href="https://sessionize.com/" target="_blank">Sessionize</a>, y de las tres opciones salió elegida justo la que más ganas tenía de dar:</p>

<figure class="align-center"><img src="/images/2023-11-13-nos-vemos-en-la-net-core-conf-2023-madrid/1.jpeg" alt="" class="image-border " style="width:65%" /></figure>

<p>¡Así que, por suerte y con muchísimo agradecimiento, os espero ver por la Netcoreconf 2023 de Madrid en las oficinas de Microsoft Ibérica!</p>]]></content><author><name>Rodrigo Liberoff</name><email>rliberoff@outlook.com</email></author><category term="Eventos" /><category term="Netcoreconf" /><category term="Inteligencia Artificial" /><category term="Semantic Kernel" /><summary type="html"><![CDATA[¡Pues al final se alinearon los planetas y las estrellas y surgió la oportunidad de poder presentar una charla en la Netcoreconf 2023 de Madrid!]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://codertectura.com//%7B%22thumbnail%22=%3E%22/images/2023-11-13-nos-vemos-en-la-net-core-conf-2023-madrid/thumbnail.png%22%7D" /><media:content medium="image" url="https://codertectura.com//%7B%22thumbnail%22=%3E%22/images/2023-11-13-nos-vemos-en-la-net-core-conf-2023-madrid/thumbnail.png%22%7D" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Nos vemos en los Microsoft 365 Live 2023</title><link href="https://codertectura.com//posts/nos-vemos-en-microsoft-365-live-2023" rel="alternate" type="text/html" title="Nos vemos en los Microsoft 365 Live 2023" /><published>2023-10-25T00:00:00+00:00</published><updated>2023-10-25T00:00:00+00:00</updated><id>https://codertectura.com//posts/nos-vemos-en-microsoft-365-live-2023</id><content type="html" xml:base="https://codertectura.com//posts/nos-vemos-en-microsoft-365-live-2023"><![CDATA[<p>¡En el último minuto se me ocurrió una idea de sesión para la cuarta edición del evento on-line Microsoft 365 Live!</p>

<p>Este es un evento de la comunidad <strong>muy</strong> presitigioso organizado desde Latinoamérica por <a href="https://www.linkedin.com/in/fchiquiza/" target="_blank">Fernando Chiquiza</a> y con participantes de muchísimos países de habla hispana.</p>

<p>En esta ocasión, a diferencia de sesiones anteriores, he decido por volcarme un poco más al mundo «<a href="https://powerapps.microsoft.com/en-us/low-code-platform/" target="_blank">Low-Code</a>» mostranado como podemos combinar <em>prompts</em> creados en Azure OpenAI con un modelo GPT-3.5-Turbo o GPT-4 con un flujo en Power Automate que puede ser utilizado por un Power Virtual Agent con el cual los usuarios podrán interactuar a través de Microsoft Teams.</p>

<p>La idea es mostrar cómo podemos proporcionar a los usuarios de una organización (principalmente privada) capacidades de Inteligencia Artificial muy semejantes a las que encontramos en ChatGPT manteniendo en todo el momento el control tanto de los consumos como de los accesos, a la vez que se mitiga significativamente el riesgo de las brechas de seguridad en la exposición de datos e información privada, sensible o confidencial, que suele ser una preocupación activa en tanto cuanto los usuarios suelen usar servicios públicos de IA (como ChatGPT) para hacer más rápido o mejor sus labores.</p>

<p>¡Vamos, que la idea es crear una especie de ChatGPT Corporativo sensillo con las herramientas de Power Platform y ponerlo a disposición de los usuarios a través de Microsoft Teams!</p>

<figure class="align-center"><img src="/images/2023-10-25-nos-vemos-en-microsoft-365-live-2023/1.jpg" alt="" class="image-border " style="width:55%" /></figure>

<p>El evento es totalmente on-line, y mi sesión será el sábado 25 de noviembre, dentro de algo que se ha denominado los «SharePoint Saturday Live 2023», aunque no hay casi nada de SharePoint en agenda 😂.</p>

<p>En caso de que no puedas ver el evento en vivo, la organización lo pondrá grabado en su canal de YouTube. En otra publicación en este blog, donde dejaré redactado los aspectos técnicos de la sesión, mencionaré donde se puede ver la misma una vez esté disponible.</p>

<p>¡Así que, poros espero ver por el Microsoft 365 Live 2023 de forma on-line!</p>]]></content><author><name>Rodrigo Liberoff</name><email>rliberoff@outlook.com</email></author><category term="Eventos" /><category term="Microsoft 365 Live" /><category term="Inteligencia Artificial" /><category term="Power Automate" /><category term="OpenAI" /><summary type="html"><![CDATA[¡En el último minuto se me ocurrió una idea de sesión para la cuarta edición del evento on-line Microsoft 365 Live!]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://codertectura.com//%7B%22thumbnail%22=%3E%22/images/2023-10-25-nos-vemos-en-microsoft-365-live-2023/thumbnail.png%22%7D" /><media:content medium="image" url="https://codertectura.com//%7B%22thumbnail%22=%3E%22/images/2023-10-25-nos-vemos-en-microsoft-365-live-2023/thumbnail.png%22%7D" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">¡La revista ByteTI ha publicado uno de mis artículos!</title><link href="https://codertectura.com//posts/articulo-revista-byte-ti" rel="alternate" type="text/html" title="¡La revista ByteTI ha publicado uno de mis artículos!" /><published>2023-10-05T00:00:00+00:00</published><updated>2023-10-05T00:00:00+00:00</updated><id>https://codertectura.com//posts/articulo-revista-byte-ti</id><content type="html" xml:base="https://codertectura.com//posts/articulo-revista-byte-ti"><![CDATA[<p><img src="https://codertectura.com//images/2023-10-05-articulo-revista-byte-ti/articulo.jpg" alt="image-right" class="align-right image-border" width="300" />
<img src="https://codertectura.com//images/2023-10-05-articulo-revista-byte-ti/portada.jpg" alt="image-right" class="align-right image-border" width="300" />
La prestigiosa revista <a href="https://revistabyte.es/" target="_blank">ByteTI</a> ha publicado en su número 319 de Octubre de 2023 una versión de mi artículo del blog «<a href="/posts/gpt-4-puede-pensar">¿Es GPT-4 capaz de pensar o ser consciente de sí mismo?</a>» y que <a href="https://blogs.encamina.com/transformacion-digital/es-gpt-4-capaz-de-pensar-o-ser-consciente-de-si-mismo/" target="_blank">originalmente publiqué en el blog</a> de ENCAMINA.</p>

<p>En esta versión, que tuve que resumir signigicativamente para que entrara en una página, dejo plasmadas las ideas principales sobre lo que entendemos por inteligencia y por consciencia y cómo estas fueron probadas contra GPT-4.</p>

<p>Puedes obtener una copia digital de la revista con el artículo <a href="https://revistabyte.es/wp-content/uploads/2023/10/Revista-Byte-TI-319-1.pdf" target="_blank">aquí</a>.</p>]]></content><author><name>Rodrigo Liberoff</name><email>rliberoff@outlook.com</email></author><category term="Divagaciones" /><category term="Inteligencia Artificial" /><summary type="html"><![CDATA[La prestigiosa revista [ByteTI](https://revistabyte.es/){:target='_blank'} ha publicado una versión de mi artículo sobre los estudios sobre inteligencia y consciencia realizados sobre GPT-4.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://codertectura.com//%7B%22thumbnail%22=%3E%22/images/2023-10-05-articulo-revista-byte-ti/thumbnail.png%22%7D" /><media:content medium="image" url="https://codertectura.com//%7B%22thumbnail%22=%3E%22/images/2023-10-05-articulo-revista-byte-ti/thumbnail.png%22%7D" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Integrando otros LLMs con Semantic Kernel</title><link href="https://codertectura.com//posts/integrando-otros-llms-con-semantic-kernel" rel="alternate" type="text/html" title="Integrando otros LLMs con Semantic Kernel" /><published>2023-07-20T00:00:00+00:00</published><updated>2023-10-29T00:00:00+00:00</updated><id>https://codertectura.com//posts/integrando-otros-llms-con-semantic-kernel</id><content type="html" xml:base="https://codertectura.com//posts/integrando-otros-llms-con-semantic-kernel"><![CDATA[<div class="notice--info" style="font-size: small; font-weight: bold;">
 
<p>Artítculo publicado originalmente en el blog <a href="https://blogs.encamina.com/piensa-en-software-desarrolla-en-colores/integrando-otros-llms-con-semantic-kernel/" target="_blank">«Piensa en software, desarrolla en colores»</a> de <a href="https://www.encamina.com/" target="_blank">ENCAMINA</a>.</p>

</div>

<p>Como seguro os habréis dado cuenta, últimamente estoy implementando muchísimos proyectos «<em>muy chulísimos</em>» (😅) con las librerías del Semantic Kernel, un marco de trabajo de código abierto (<em>open source</em>) que principalmente se enfoca en facilitar la combinación de mensajes e indicaciones – los famosos <em>prompts</em> – para Inteligencias Artificiales generativas basadas en las tecnologías de OpenAI.</p>

<p>Y ahí hay un punto importante y es que, en su estado actual, Semantic Kernel por ahora sólo opera únicamente con OpenAI, ya sea la variante de Azure o la propia de OpenAI.</p>

<p>Pero… ¿y si queremos usar un modelo que no sea de OpenAI con Semantic Kernel? ¿Si queremos usar recursos de otro proveedor de Inteligencia Artificial?</p>

<p>¡Quédate conmigo que te explico cómo podemos integrar modelos de otros LLMs (<em>Large Language Models</em>) como los disponibles en <a href="https://huggingface.co/deepset/roberta-base-squad2" target="_blank">Hugging Face</a> en nuestros proyectos de IA usando Funciones Nativas y <em>plugins</em> de Semantic Kernel!</p>

<p>El código fuente de este artículo lo podéis conseguir en GitHub, aquí 👉🏻 <a href="https://github.com/rliberoff/BLOG-001-Semantic-Kernel-Native-Functions" target="_blank">https://github.com/rliberoff/BLOG-001-Semantic-Kernel-Native-Functions</a>.</p>

<h3 id="creando-una-función-nativa-para-la-integración-con-otro-llm">Creando una Función Nativa para la integración con otro LLM</h3>

<p>En Semantic Kernel, las Funciones Nativas son básicamente código en nuestro lenguaje de programación de elección, en mi caso C#.</p>

<p>Para crear una Función Nativa, primero debemos definir un <em>plugin</em>. Esto es algo súper sencillo, básicamente es crear un directorio dentro de nuestro proyecto en el cual dejaremos clases que representarán a nuestros plugins con métodos que representarán a nuestras funciones.</p>

<figure class="align-center"><img src="/images/2023-07-20-integrando-otros-llms-con-semantic-kernel/1.png" alt="" class=" " style="" /></figure>

<p>En nuestro caso, vamos a realizar una integración con «<code class="language-plaintext highlighter-rouge">roberta-base-squad2</code>» disponible en <a href="https://huggingface.co/deepset/roberta-base-squad2" target="_blank">Hugging Face</a>. Este modelo nos permite integrarnos con un LLM llamado <a href="https://arxiv.org/abs/1907.11692" target="_blank">RoBERTa</a> (nombre gracioso para <em>Robustly Optimized BERT Approach</em>) desarrollado por la empresa <a href="https://www.deepset.ai/" target="_blank">deepset</a> (los mismos de <a href="https://haystack.deepset.ai/" target="_blank">Haystack</a>).</p>

<p>El modelo «<code class="language-plaintext highlighter-rouge">roberta-base-squad2</code>» nos permitirá realizar preguntas que serán contestadas a partir del contenido de un contexto que también debemos suministrar. El resultado retornado nos dará la respuesta junto con un valor numérico que indicará el índice de confiabilidad de la calidad de dicha respuesta, es decir, un score cuyo valor cuánto más alto sea indicará que la respuesta es de mejor calidad y, por el contrario, cuanto más bajo sea que la respuesta es menos confiable.</p>

<p>Para esta integración es fundamental que tengas una cuenta en Hugging Face, ya que necesitarás un token para autenticarte al realizar las llamadas al API REST que nos da conectividad con el modelo de «<code class="language-plaintext highlighter-rouge">roberta-base-squad2</code>». Perfectamente te valdrá una cuenta gratuita para esto 😏</p>

<p>Para gestionar el token de Hugging Face crearemos una clase de opciones llamada <code class="language-plaintext highlighter-rouge">HuggingFaceOptions</code> con una propiedad Token que inicializaremos con un valor que obtendremos del fichero de configuración (<code class="language-plaintext highlighter-rouge">appsetttings.json</code>).</p>

<figure class="align-center"><img src="/images/2023-07-20-integrando-otros-llms-con-semantic-kernel/2.png" alt="" class=" " style="" /></figure>

<p>Recordemos que las clases de opciones las configuramos con inyección de dependencias en el archivo <code class="language-plaintext highlighter-rouge">Program.cs</code>:</p>

<figure class="align-center"><img src="/images/2023-07-20-integrando-otros-llms-con-semantic-kernel/3.png" alt="" class=" " style="" /></figure>

<p>Con esta parte de la carpintería básica ya montada, el siguiente paso será crear el directorio <code class="language-plaintext highlighter-rouge">Plugins</code> (no somos muy originales con los nombres 😅) y dentro de éste crearemos una clase llamada <code class="language-plaintext highlighter-rouge">HuggingFaceDeepsetRobertaQuestionsAnsweringPlugin</code> (de nuevo los nombres no son mi fuerte 😅). Esta clase representa al <em>plugin</em> que nos dará conectividad con el modelo, y cada uno de sus métodos públicos serán las Funciones Nativas que podemos utilizar con Semantic Kernel.</p>

<p>En nuestro caso sólo tendremos un método llamado <code class="language-plaintext highlighter-rouge">AskQuestionWithContextAsync</code> para integrarnos al modelo «<code class="language-plaintext highlighter-rouge">roberta-base-squad2</code>». Este método toma dos variables: el contexto y la pregunta, las cuales son usadas para armar la llamada al modelo «<code class="language-plaintext highlighter-rouge">roberta-base-squad2</code>» mediante un API REST de Hugging Face (líneas 30 a 37).</p>

<figure class="align-center"><a href="https://codertectura.com//images/2023-07-20-integrando-otros-llms-con-semantic-kernel/4.png" target="_blank" rel="noopener" data-lity="">
            <img src="/images/2023-07-20-integrando-otros-llms-con-semantic-kernel/4.png" alt="" class=" " style="" />
        </a><figcaption>
        Haz click para ver la imagen más grande.

    </figcaption></figure>

<p>Un detalle importante con las funciones en Semantic Kernel (sean Nativas o Semánticas) es que todo se gestiona con <em>strings</em>. Esto quiere decir que lo que retorne nuestra Función Nativa (el método <code class="language-plaintext highlighter-rouge">AskQuestionWithContextAsync</code>) sólo puede ser un <code class="language-plaintext highlighter-rouge">string</code>. La parte interesante, es que podemos devolver un JSON como <code class="language-plaintext highlighter-rouge">string</code> que después podemos parsear para crear un objeto más concreto y específico. Por esa razón es que, como ves en el código, se retorna directamente el contenido de la respuesta recibida de la llamada al API REST.</p>

<p><strong>Y como puedes apreciar, ¡no hay magia!</strong> La integración con otros LLMs en Semantic Kernel la podemos hacer de formas tan pueriles o triviales como llamadas a un API REST, o integrarnos con un SDK que puedan ofrecernos a través de paquetes tales como los NuGet.</p>

<p>Ahora tenemos que configurar Semantic Kernel para que pueda hacer uso de nuestra Función Nativa.</p>

<h3 id="configurando-semantic-kernel-con-la-función-nativa">Configurando Semantic Kernel con la Función Nativa</h3>

<p>Configurar el Semantic Kernel en nuestros proyectos es realmente sencillo. Todo se hace (habitualmente) en el <code class="language-plaintext highlighter-rouge">Program.cs</code>.</p>

<figure class="align-center"><a href="https://codertectura.com//images/2023-07-20-integrando-otros-llms-con-semantic-kernel/5.png" target="_blank" rel="noopener" data-lity="">
            <img src="/images/2023-07-20-integrando-otros-llms-con-semantic-kernel/5.png" alt="" class=" " style="" />
        </a><figcaption>
        Haz click para ver la imagen más grande.

    </figcaption></figure>

<p>Nuestra Función Semántica la hemos codificado para que perfectamente pueda ser registrada en el contenedor de dependencias de .NET como un singleton, lo cual nos ayudará ligueramente con el desempeño del código.</p>

<p>Al configurar una instancia de Semantic Kernel, es recomendable para escenarios web que se registre como <em>Scoped</em>, ya que internamente el Semantic Kernel gestiona estados y contextos que en escenarios compartidos (usualmente web) podrían producir efectos secundarios y comportamientos inesperados no deseados si se registra como <em>Singleton</em>. También recuerda que es siempre una buena idea configurar un logger con el Semantic Kernel, pues así podrás saber qué ha podido salir mal en caso de errores; a mí, particularmente me gusta usar Azure Application Insights como servicio para mis trazas y registros de eventos, y podrás ver cómo lo he configurado en el código del <code class="language-plaintext highlighter-rouge">Program.cs</code> disponible en el repo de <a href="https://github.com/rliberoff/BLOG-001-Semantic-Kernel-Native-Functions" target="_blank">GitHub</a>.</p>

<p>Dentro de la configuración, una vez que tenemos un <code class="language-plaintext highlighter-rouge">kernel</code> establecido, importamos la Función Nativa (líneas 11 y 12) proporcionando una instancia de ésta y un nombre para la <em>skill</em> que representa.</p>

<p>No es nuestro caso, pero puede ocurrir que tengas Funciones Nativas complejas, con inicializaciones complicadas o costosas en términos de recursos. Si eso ocurre, mi recomendación es que inviertas cierto tiempo y esfuerzo en implementar un patrón <code class="language-plaintext highlighter-rouge">Factory</code> para la <em>skill</em>, y que pases una instancia de dicho <code class="language-plaintext highlighter-rouge">Factory</code> al Semantic Kernel.</p>

<p>Lo que queda ahora es poder utilizar esta Función Nativa y manipular el string que retorna para devolver algo más significativo.</p>

<h3 id="llamando-a-nuestra-función-nativa-para-usar-el-llm">Llamando a nuestra Función Nativa para usar el LLM</h3>

<p>Para probar nuestra integración, vamos a crear un <code class="language-plaintext highlighter-rouge">Controller</code> con una acción para exponer desde un API REST un endpoint para consumir nuestro LLM.</p>

<p>Y no sólo eso, sino que también nos permita interpretar el resultado para saber a partir del score si vale la pena retornar la respuesta, o mejor contestar un “<em>no sé</em>”.</p>

<figure class="align-center"><a href="https://codertectura.com//images/2023-07-20-integrando-otros-llms-con-semantic-kernel/6.png" target="_blank" rel="noopener" data-lity="">
            <img src="/images/2023-07-20-integrando-otros-llms-con-semantic-kernel/6.png" alt="" class=" " style="" />
        </a><figcaption>
        Haz click para ver la imagen más grande.

    </figcaption></figure>

<p>Para esta demo, estoy estableciendo el valor mínimo del <em>score</em> de la respuesta en un 0.7 (un 70% de confiabilidad) para considerarla como válida. Lo idea es que este valor esté parametrizado en una clase de opciones parecida a la que vimos como ejemplo antes con el caso de <code class="language-plaintext highlighter-rouge">HuggingFaceOptions</code>. De momento, nos vale con tenerlo como una constante dentro de la acción.</p>

<p>Tomaremos la pregunta y el contexto que nos llegarán por request (clase <code class="language-plaintext highlighter-rouge">AskRequest</code>) y sus valores los usaremos en una instancia de la clase <code class="language-plaintext highlighter-rouge">ContextVariables</code> que pasaremos como parámetros al Semantic Kernel (representado por una instancia válida del tipo de interface <code class="language-plaintext highlighter-rouge">IKernel</code> que recibimos como dependencia en el constructor del controlador).</p>

<p>Para llamar a nuestra Función Nativa registrada en el Semantic Kernel simplemente tenemos que suministrar el nombre de la <em>skill</em> y de la función (líneas 25 a 30). El resultado de la ejecución de la función es una instancia del tipo <code class="language-plaintext highlighter-rouge">SKContext</code>, la cual nos ofrece variada información sobre el resultado de dicha ejecución.</p>

<p>Así, con el resultado obtenido, primero verificamos que no haya ocurrido ningún error (excepción), y en caso de haberlo, retornar el mensaje del error correspondiente (líneas 32 a 35).</p>

<p>Si todo ha ido bien, tomamos la respuesta que nos ha llegado como tipo <code class="language-plaintext highlighter-rouge">string</code> y que sabemos de la implementación que realmente es un JSON con la respuesta del modelo «<code class="language-plaintext highlighter-rouge">roberta-base-squad2</code>». Para obtener los valores de la respuesta, he creado la clase <code class="language-plaintext highlighter-rouge">AskResponse</code> que mapea “1 a 1” con algunos de los valores del JSON retornado por «<code class="language-plaintext highlighter-rouge">roberta-base-squad2</code>»; y de los cuales nos interesan dos en concreto: la respuesta y su score. Usando el JsonSerializer podemos obtener la instancia de AskResponse con los valores que nos interesan.</p>

<p>Finalmente, verificamos si el <em>score</em> recibido es mayor al valor mínimo que hemos establecido. De ser así, retornamos la respuesta, sino retornamos un <code class="language-plaintext highlighter-rouge">HTTP 404 Not Found</code> con un mensaje indicando que no se consiguió una respuesta satisfactoria porque el <em>score</em> era bajo.</p>

<p>Para probar esto, os dejo una colección de Postman con esta llamada y que está disponible en el repo en <a href="https://github.com/rliberoff/BLOG-001-Semantic-Kernel-Native-Functions" target="_blank">GitHub</a> mencionado al principio del artículo. Abajo tienes dos imágenes de una respuesta correcta y otra de “no sé” 👇🏻</p>

<figure class="align-center"><a href="https://codertectura.com//images/2023-07-20-integrando-otros-llms-con-semantic-kernel/7.png" target="_blank" rel="noopener" data-lity="">
            <img src="/images/2023-07-20-integrando-otros-llms-con-semantic-kernel/7.png" alt="" class=" " style="" />
        </a></figure>

<figure class="align-center"><a href="https://codertectura.com//images/2023-07-20-integrando-otros-llms-con-semantic-kernel/8.png" target="_blank" rel="noopener" data-lity="">
            <img src="/images/2023-07-20-integrando-otros-llms-con-semantic-kernel/8.png" alt="" class=" " style="" />
        </a><figcaption>
        Haz click para ver las imagenes más grandes.

    </figcaption></figure>

<h3 id="más-información">Más información</h3>

<p>Si tienes más curiosidad sobre Semantic Kernel, te dejo aquí algunas publicaciones de mi <a href="https://www.youtube.com/@CODERTECTURA" target="_blank">YouTube</a> donde justamente trato sobre estos temas:</p>

<ul>
  <li>Configuración paso a paso del Semantic Kernel 👉🏻 <a href="https://youtu.be/a8gNdF0D23g" target="_blank">https://youtu.be/a8gNdF0D23g</a></li>
</ul>

<div class="responsive-embed responsive-embed-16by9 image-border" style="margin-top: 20px; margin-left: 50px; width:55%">
    <iframe src="https://www.youtube.com/embed/a8gNdF0D23g" frameborder="0" title="SEMANTIC KERNEL - Configuración PASO a PASO 👣" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="">        
    </iframe>
</div>

<ul>
  <li>Las Funciones Semánticas en Semantic Kernel 👉🏻 <a href="https://youtu.be/jc6H8gmXAAA" target="_blank">https://youtu.be/jc6H8gmXAAA</a></li>
</ul>

<div class="responsive-embed responsive-embed-16by9 image-border" style="margin-top: 20px; margin-left: 50px; width:55%">
    <iframe src="https://www.youtube.com/embed/jc6H8gmXAAA" frameborder="0" title="Te eneseño cómo son las Funciones Semánticas en SEMANTIC KERNEL" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="">        
    </iframe>
</div>

<ul>
  <li>Las Funciones Nativas en Semantic Kernel 👉🏻 <a href="https://youtu.be/mSJa0oaS_XE" target="_blank">https://youtu.be/mSJa0oaS_XE</a></li>
</ul>

<div class="responsive-embed responsive-embed-16by9 image-border" style="margin-top: 20px; margin-left: 50px; width:55%">
    <iframe src="https://www.youtube.com/embed/mSJa0oaS_XE" frameborder="0" title="💥🫵🏻 FUNCIONES NATIVAS en SEMANTIC KERNEL, y TODO lo que necesitas para implementarlas‼️" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="">        
    </iframe>
</div>]]></content><author><name>Rodrigo Liberoff</name><email>rliberoff@outlook.com</email></author><category term="Tutorial" /><category term="C#" /><category term="Inteligencia Artificial" /><category term="Semantic Kernel" /><summary type="html"><![CDATA[Usando Semantic Kernel podemos integrar diversos tipos de Large Language Models (LLMs), no sólo los de OpenAI, en nuestras soluciones de inteligencia artificial.En esta publicación te muestro cómo lograrlo.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://codertectura.com//images/2023-07-20-integrando-otros-llms-con-semantic-kernel/header.png" /><media:content medium="image" url="https://codertectura.com//images/2023-07-20-integrando-otros-llms-con-semantic-kernel/header.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Publicación en revista CompatiMOSS #56</title><link href="https://codertectura.com//posts/articulo-compartimoss-56" rel="alternate" type="text/html" title="Publicación en revista CompatiMOSS #56" /><published>2023-06-30T00:00:00+00:00</published><updated>2023-11-03T00:00:00+00:00</updated><id>https://codertectura.com//posts/articulo-en-compartimoss</id><content type="html" xml:base="https://codertectura.com//posts/articulo-compartimoss-56"><![CDATA[<p>He tenido el honor y el placer de publicar un artículo en la revista <a href="https://www.compartimoss.com/" target="blank">CompartiMOSS</a> sobre cómo montar una arquitectura 100% «Cloud Native» para Inteligencia Artificial usando <a href="https://learn.microsoft.com/en-us/semantic-kernel/overview/" target="_blank">Semantic Kernel</a> y <a href="https://qdrant.tech/" target="_blank">Qdrant</a>.</p>

<p>Podéís leer el artículo completo aquí 👉🏻 <a href="https://www.compartimoss.com/revistas/numero-56/cloud-native-semantic-kernel/" target="blank">https://www.compartimoss.com/revistas/numero-56/cloud-native-semantic-kernel/</a>.</p>

<p><img src="https://codertectura.com//images/2023-06-30-articulo-en-compartimoss/1.jpg" alt="image-center" class="align-center image-border" /></p>]]></content><author><name>Rodrigo Liberoff</name><email>rliberoff@outlook.com</email></author><category term="Tutorial" /><category term="C#" /><category term="Inteligencia Artificial" /><category term="Semantic Kernel" /><category term="CompartiMOSS" /><summary type="html"><![CDATA[He tenido el honor y el placer de publicar un artículo en la revista CompartiMOSS sobre cómo montar una arquitectura 100% «Cloud Native» para Inteligencia Artificial usando Semantic Kernel y Qdrant.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://codertectura.com//images/2023-06-30-articulo-en-compartimoss/header.png" /><media:content medium="image" url="https://codertectura.com//images/2023-06-30-articulo-en-compartimoss/header.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">CompartiMOSS Unplugged sobre Inteligencia Artificial #4</title><link href="https://codertectura.com//posts/compartimoss-unplugged-ai-04" rel="alternate" type="text/html" title="CompartiMOSS Unplugged sobre Inteligencia Artificial #4" /><published>2023-06-29T00:00:00+00:00</published><updated>2023-11-03T00:00:00+00:00</updated><id>https://codertectura.com//posts/compartimoss-unplugged-ai-04</id><content type="html" xml:base="https://codertectura.com//posts/compartimoss-unplugged-ai-04"><![CDATA[<p>Tras una breve ausencia en la tercera sesión, regreso para la cuarta entrega del <em>spin-off</em> de las sesiones de <em>streaming</em> de CompartiMOSS sobre Inteligencia Artificial. En esta ocasión hemos conversado sobre las novedades de Meta, la empresa que antes se llamaba Facebook.</p>

<p>Durante nuestra sesión, analizamos dos proyectos súper interesantes:</p>

<ul>
  <li><a href="https://ai.meta.com/blog/yann-lecun-ai-model-i-jepa/" target="_blank">I-JEPA</a>, una inteligencia artificial con visión humana.</li>
  <li><a href="https://voicebox.metademolab.com/" target="_blank">Voicebox</a>, una herramienta que permite generar voces sintéticas personalizadas y realistas.</li>
</ul>

<p>La sesión ya está disponible en YouTube y la podéis disfrutar aquí 👉🏻</p>

<div class="responsive-embed responsive-embed-16by9 image-border" style="margin-top: 20px;">
    <iframe src="https://www.youtube.com/embed/a7P8ACtzGkQ?si=o3MKqWfbxlth91Ya" frameborder="0" title="Unplugged AI 04: Meta I-JEPA y Voicebox" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="">        
    </iframe>
</div>]]></content><author><name>Rodrigo Liberoff</name><email>rliberoff@outlook.com</email></author><category term="Inteligencia Artificial" /><category term="CompartiMOSS" /><summary type="html"><![CDATA[Tras una breve ausencia en la tercera sesión, regreso para la cuarta entrega del *spin-off* de las sesiones de *streaming* de CompartiMOSS sobre Inteligencia Artificial, en esta ocasión para hablar sonbre las novedades de Meta y sus proyectos I-JEPA y Voicebox.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://codertectura.com//%7B%22thumbnail%22=%3E%22/images/2023-06-29-compartimoss-unplugged-ai-04/thumbnail.png%22%7D" /><media:content medium="image" url="https://codertectura.com//%7B%22thumbnail%22=%3E%22/images/2023-06-29-compartimoss-unplugged-ai-04/thumbnail.png%22%7D" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">CompartiMOSS Unplugged sobre Inteligencia Artificial #2</title><link href="https://codertectura.com//posts/compartimoss-unplugged-ai-02" rel="alternate" type="text/html" title="CompartiMOSS Unplugged sobre Inteligencia Artificial #2" /><published>2023-06-01T00:00:00+00:00</published><updated>2023-11-03T00:00:00+00:00</updated><id>https://codertectura.com//posts/compartimoss-unplugged-ai-02</id><content type="html" xml:base="https://codertectura.com//posts/compartimoss-unplugged-ai-02"><![CDATA[<p>En esta segunda entrega del <em>spin-off</em> de las sesiones de <em>streaming</em> de CompartiMOSS sobre Inteligencia Artificial hemos conversado sobre las novedades de AI del Microsoft Build 2023, principalmente de:</p>

<ul>
  <li>El nuevo servicio de moderación de contenido «Azure Content Safety»</li>
  <li>«Prompt Flow», el nuevo diseñador de <em>prompts</em> para algortimos como GPT dentro de Azure Machine Learning.</li>
</ul>

<p>La sesión ya está disponible en YouTube y la podéis disfrutar aquí 👉🏻</p>

<div class="responsive-embed responsive-embed-16by9 image-border" style="margin-top: 20px;">
    <iframe src="https://www.youtube.com/embed/C1yMYU4E3-U?si=zlDPeqPVFCGLrAXn" frameborder="0" title="Unplugged AI 02: Azure Content Safety, Azure ML Prompt Flow" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="">        
    </iframe>
</div>]]></content><author><name>Rodrigo Liberoff</name><email>rliberoff@outlook.com</email></author><category term="Inteligencia Artificial" /><category term="CompartiMOSS" /><category term="Microsoft Build" /><summary type="html"><![CDATA[Segunda entrega del *spin-off* de las sesiones de *streaming* de CompartiMOSS sobre Inteligencia Artificial, donde hablamos sobre las novedades de AI del Microsoft Build: El nuevo servicio de moderación de contenido Azure Content Safety, y del nuevo diseñador de prompt de Azure ML, Prompt Flow.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://codertectura.com//%7B%22thumbnail%22=%3E%22/images/2023-06-01-compartimoss-unplugged-ai-02/thumbnail.png%22%7D" /><media:content medium="image" url="https://codertectura.com//%7B%22thumbnail%22=%3E%22/images/2023-06-01-compartimoss-unplugged-ai-02/thumbnail.png%22%7D" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Cómo monetizar nuestras APIs de Inteligencia Artificial con Azure API Management - Parte 3: Delegación en el APIM</title><link href="https://codertectura.com//posts/apis-monetizacion-parte-3" rel="alternate" type="text/html" title="Cómo monetizar nuestras APIs de Inteligencia Artificial con Azure API Management - Parte 3: Delegación en el APIM" /><published>2023-05-31T00:00:00+00:00</published><updated>2023-10-29T00:00:00+00:00</updated><id>https://codertectura.com//posts/moneitzar-apis-inteligencia-artificial-parte-3</id><content type="html" xml:base="https://codertectura.com//posts/apis-monetizacion-parte-3"><![CDATA[<div class="notice--info" style="font-size: small; font-weight: bold;">
 
<p>Artítculo publicado originalmente en el blog <a href="https://blogs.encamina.com/piensa-en-software-desarrolla-en-colores/como-monetizar-nuestras-apis-de-inteligencia-artificial-con-el-azure-api-management-3a-parte/" target="_blank">«Piensa en software, desarrolla en colores»</a> de <a href="https://www.encamina.com/" target="_blank">ENCAMINA</a>.</p>

</div>

<div class="notice--success" style="font-size: medium;">
  
<p>Este es la primera parte de una serie de trest publicaciones:</p>

<ul>
  <li><a href="/posts/apis-monetizacion-parte-1">Parte 1: Introducción</a></li>
  <li><a href="/posts/apis-monetizacion-parte-2">Parte 2: Implementación</a></li>
</ul>

</div>

<p>El Azure API Management ofrece la capacidad de delegación, la cual permite que un sitio web externo gestione la información de los usuarios, en lugar de utilizar las funciones integradas del Developers Portal. En este artículo, nos centraremos específicamente en la delegación de la gestión de suscripciones a productos en Azure Api Management.</p>

<h3 id="configurando-del-azure-api-management">Configurando del Azure API Management</h3>

<p>El Azure API Management (APIM) proporciona una capacidad llamada «Delegación», la cual sólo está disponible en los planes “Developer”, “Basic”, “Standard” y “Premium” del  recurso.</p>

<p>Gracias a esta capacidad, el APIM permite que un sitio web externo se apropie de los datos de los usuarios desde el Developers Portal y realice una validación personalizada. Con la  delegación, por ahora, se pueden controlar aspectos tales como el registro e inicio de  sesión de usuarios en el Developers Portal, y la gestión de subscripciones a productos, en lugar de que se emplee la funcionalidad integrada del propio Developers Portal.</p>

<p>Para esta publicación, nos interesa concretamente la delegación de la gestión de las  subscripciones a productos, la cual sigue el siguiente flujo de trabajo:</p>

<ol>
  <li>El usuario selecciona un producto en el Developers Portal del APIM y hace clic en el botón Suscribir.</li>
  <li>El navegador se redirige al endpoint de delegación (en nuestro caso una aplicación desplegada en un Azure Container Applicationo ACA).</li>
  <li>El endpoint de delegación realiza los pasos necesarios para la suscripción al producto. Estos pueden incluir lo siguiente:
    <ul>
      <li>Redirigir a otra página para solicitar información de facturación y de pago (como tarjeta de crédito).</li>
      <li>Hacer preguntas adicionales.</li>
      <li>Almacenar la información adicional relevante para la facturación.</li>
    </ul>
  </li>
</ol>

<p>La delegación de servicios del APIM se puede realizar fácilmente desde el Azure Portal como se muestra en la siguiente imagen:</p>

<figure class="align-center"><a href="https://codertectura.com//images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/1.png" target="_blank" rel="noopener" data-lity="">
            <img src="/images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/1.png" alt="" class="image-border " style="" />
        </a><figcaption>
        Haz click para ver la imagen más grande.

    </figcaption></figure>

<p>Es fundamental saber que no existe una “Delegation Validation Key” hasta tanto no le  demos al botón de generar y salvemos los cambios. Una vez hecho eso, guardar en un lugar seguro el valor del “Delegation Validation Key” ya que lo necesitaremos más adelante (aunque perfectamente podemos volver a esta pantalla para recuperarlo o generar uno nuevo).</p>

<h3 id="recibiendo-la-delegación-de-subscripciones--desde-el-azure-api-management">Recibiendo la delegación de subscripciones <br /> desde el Azure API Management</h3>

<p>La delegación de capacidades desde el Azure API Management (APIM) requiere la implementación de una aplicación web, no de una API (REST) como podríamos pensar. Es fundamental que la aplicación proporcione las pantallas necesarias para que los usuarios puedan suministrar la información que necesitemos como relevante para nuestra gestión de sus subscripciones. Para este ejemplo, y la integración con Stripe, sólo necesitamos que el usuario nos suministre el nombre de la subscripción.</p>

<p>Esta aplicación web la podemos programar con el lenguaje de programación que mejor se adapte a nuestra forma de trabajar, y que se puede desplegar en cualquier tipo de recursos que consideremos apropiado.</p>

<p>Para esta demo, he querido crear una arquitectura totalmente «Cloud Native», por lo cual elegí desarrollar la aplicación web como una imagen Docker que perfectamente podríamos desplegar en un Azure Web Application for Docker, un Azure Container Instance o un Azure Kubernetes Service; sin embargo, he elegido la opción más de moda en cuanto a rendimiento, costo y capacidades que son las Azure Container Applications (ACA).</p>

<p>Como leguaje de programación he elegido C# con .NET 6 porque es mi favorito, aunque todo lo implementado es perfectamente realizable con otros lenguajes.</p>

<p>En el ejemplo he decidido usar ASP.NET MVC con Razor como tecnología de implementación.</p>

<p>Recordemos que Azure, y más aún nuestra aplicación, no estarán certificadas en <a href="https://www.pcisecuritystandards.org/" target="_blank">PCI-DSS</a>, razón por la cual tenemos que redirigir a las pantallas de Stripe (fuera del contexto de nuestra aplicación y de Azure) para obtener esta información. Otra alternativa es incrustar las pantallas de Stripe en un <code class="language-plaintext highlighter-rouge">&lt;iframe\&gt;</code> pero muchas veces encuentro eso más laborioso e innecesario.</p>

<p>Como nuestra aplicación se va a integrar con Stripe, primero es necesario tener una referencia a su API a través de una librería que nos permite usarla con C#. Para ello nos basta con usar el paquete NuGet que nos proporciona la misma gente de Stripe aquí: <a href="https://www.nuget.org/packages/Stripe.net/" target="_blank">https://www.nuget.org/packages/Stripe.net/</a>.</p>

<p>Luego, necesitamos configurar algunos parámetros que conservaremos en el <code class="language-plaintext highlighter-rouge">appsettings.json</code> de nuestra aplicación y que recuperaremos con una clase de opciones como la siguiente:</p>

<figure class="align-center"><a href="https://codertectura.com//images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/2.png" target="_blank" rel="noopener" data-lity="">
            <img src="/images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/2.png" alt="" class=" " style="" />
        </a><figcaption>
        Haz click para ver la imagen más grande.

    </figcaption></figure>

<p>Necesitaremos:</p>

<ul>
  <li>Una nueva «Restricted Keys» que podemos crear desde el portal del desarrollador de Stripe, que podemos llamar «App Key» y con permisos otorgados para leer precios y productos, y para poder leer y escribir subscripciones, registros de uso y sesiones de pago (<em>checkout</em>).</li>
  <li>El valor de la «Publishable key» que ya nos da Stripe como parte de sus «Standard keys».</li>
  <li>El secreto del <em>webhook</em>, ya sea porque lo conservamos de la ejecución del <em>script</em> de configuración de Stripe, o porque lo recuperamos desde el portal del desarrollador.</li>
</ul>

<p>Para trabajar con el APIM desde código C#, lo más conveniente es usar el paquete NuGet que nos proporciona Microsoft aquí: <a href="https://www.nuget.org/packages/Azure.ResourceManager.ApiManagement" target="_blank">https://www.nuget.org/packages/Azure.ResourceManager.ApiManagement</a>.</p>

<p>Para poder usar las librerías de este paquete necesitamos ciertos valores a obtener de nuestra instancia del APIM a conservar en el <code class="language-plaintext highlighter-rouge">appsettings.json</code> de nuestra aplicación y que recuperaremos con una clase de opciones como la siguiente:</p>

<figure class="align-center"><a href="https://codertectura.com//images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/3.png" target="_blank" rel="noopener" data-lity="">
            <img src="/images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/3.png" alt="" class=" " style="" />
        </a><figcaption>
        Haz click para ver la imagen más grande.

    </figcaption></figure>

<p>Necesitaremos:</p>

<ul>
  <li>El valor del «Delegation Validation Key» generado al configurar la delegación de capacidades del APIM en el Azure Portal.</li>
  <li>La URL al Developers Portal.</li>
</ul>

<figure class="align-center"><a href="https://codertectura.com//images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/4.png" target="_blank" rel="noopener" data-lity="">
            <img src="/images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/4.png" alt="" class="image-border " style="" />
        </a><figcaption>
        Haz click para ver la imagen más grande.

    </figcaption></figure>

<ul>
  <li>La URL para la gestión del APIM, que se puede obtener como se muestra en la siguiente imagen. También es importante encenderla.</li>
</ul>

<figure class="align-center"><a href="https://codertectura.com//images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/5.png" target="_blank" rel="noopener" data-lity="">
            <img src="/images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/5.png" alt="" class="image-border " style="" />
        </a><figcaption>
        Haz click para ver la imagen más grande.

    </figcaption></figure>

<ul>
  <li>Por último, necesitamos el nombre de la instancia del APIM, el nombre del grupo de recursos donde la hemos desplegado y el identificador de la subscripción de Azure a la que pertenece dicho grupo de recursos.</li>
</ul>

<p>Con este paquete agregado a nuestro proyecto de aplicación web, y los valores de integración ya configurados, sólo necesitamos registrarlos en nuestro <code class="language-plaintext highlighter-rouge">Program.cs</code> de la siguiente forma:</p>

<figure class="align-center"><a href="https://codertectura.com//images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/6.png" target="_blank" rel="noopener" data-lity="">
            <img src="/images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/6.png" alt="" class=" " style="" />
        </a><figcaption>
        Haz click para ver la imagen más grande.

    </figcaption></figure>

<p>Cada vez que el APIM envíe una delegación de operaciones, lo hará a una ruta que hayamos definido al configurar la delegación, y siempre acompañada de un parámetro (por query string) llamado <code class="language-plaintext highlighter-rouge">operation</code> que identifica la operación que el usuario quiere realizar.</p>

<p>Lo recomendable aquí es que tengamos un solo controlador para recibir las peticiones desde el APIM que se encargue de extraer el valor del parámetro <code class="language-plaintext highlighter-rouge">operation</code> para enrutar al siguiente controlador que se encargará de efectivamente ejecutar la lógica de la operación.</p>

<p>En el código de ejemplo encontrarás dentro del directorio <code class="language-plaintext highlighter-rouge">Pages</code> la página <code class="language-plaintext highlighter-rouge">Index.cshtml</code> que contiene la lógica de enrutamiento de las peticiones que llegan desde el APIM.</p>

<p>En concreto nos interesan dos operaciones:</p>

<ul>
  <li><strong><em>Subscribe</em></strong> → recibida cuando se está creando una nueva subscripción a productos desde el Developers Portal.</li>
  <li><strong><em>Unsubscribe</em></strong> → recibida cuando se cancela una subscripción activa desde el Developers Portal.</li>
</ul>

<p>Ahora procedemos a crear una interfaz que defina las operaciones que realizaremos desde código con C# sobre el Azure API Management usando el paquete NuGet importado anteriormente.</p>

<figure class="align-center"><a href="https://codertectura.com//images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/7.png" target="_blank" rel="noopener" data-lity="">
            <img src="/images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/7.png" alt="" class=" " style="" />
        </a><figcaption>
        Haz click para ver la imagen más grande.

    </figcaption></figure>

<p>Cada una de estas operaciones están implementadas en la clase <code class="language-plaintext highlighter-rouge">ApiService</code>, la cual usamos como un Singleton que registramos en nuestro contenedor de dependencias en el <code class="language-plaintext highlighter-rouge">Program.cs</code>.</p>

<p>El constructor de la clase <code class="language-plaintext highlighter-rouge">ApiService</code> se encarga de crear una instancia de la clase ArmClient (que obtenemos del paquete NuGet) la cual a su vez necesita de las credenciales de Azure para operar correctamente.</p>

<figure class="align-center"><a href="https://codertectura.com//images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/8.png" target="_blank" rel="noopener" data-lity="">
            <img src="/images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/8.png" alt="" class=" " style="" />
        </a><figcaption>
        Haz click para ver la imagen más grande.

    </figcaption></figure>

<p>La clase <code class="language-plaintext highlighter-rouge">ArmClient</code> es como un cliente agnóstico del tipo de servicio o recurso de Azure que vamos a gestionar. Para identificar el recurso del APIM necesitamos ciertos parámetros que hemos configurado en el appsettings.json y que obtenemos de la clase de opciones ApimServiceOptions, que son el identificador de la subscripción de Azure, el nombre del grupo de recursos y el nombre del servicio, los cuales usaremos para crear un identificador del recurso APIM gracias al método <code class="language-plaintext highlighter-rouge">CreateResourceIdentifier</code> de la clase <code class="language-plaintext highlighter-rouge">ApiManagementServiceResource</code> la cual representa un APIM junto con las operaciones de instancia que se pueden realizar el mismo.</p>

<figure class="align-center"><a href="https://codertectura.com//images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/9.png" target="_blank" rel="noopener" data-lity="">
            <img src="/images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/9.png" alt="" class=" " style="" />
        </a><figcaption>
        Haz click para ver la imagen más grande.

    </figcaption></figure>

<p>En el código anterior se obtiene una instancia de <code class="language-plaintext highlighter-rouge">ApiManagementServiceResource</code>, que sirve para gestionar aspectos tales como las subscripciones. Sin embargo, existen muchas más como <code class="language-plaintext highlighter-rouge">ApiManagementProductResource</code> que sirve para gestionar productos (por ejemplo, obtenerlos a partir de su identificador), <code class="language-plaintext highlighter-rouge">ApiManagementUserResource</code> que sirve para gestionar los usuarios creados desde el Developer Portal, entre otros.</p>

<p>Cada método de la interface <code class="language-plaintext highlighter-rouge">IApimService</code> hará uso de la instancia del <code class="language-plaintext highlighter-rouge">ArmClient</code> creada en el constructor de <code class="language-plaintext highlighter-rouge">ApimService</code> así como del identificador de creado por <code class="language-plaintext highlighter-rouge">ApiManagementUserResource</code> para obtener una instancia del servicio que se necesite para cada operatividad.</p>

<p>La interface (y su implementación) definen las siguientes operaciones:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">CancelSubscriptionAsync</code> → Se encarga de cancelar una subscripción, ya sea porque el usuario lo especifica de esta forma desde el Developer Portal, o porque es cancelada administrativamente desde el portal de Stripe.</li>
  <li><code class="language-plaintext highlighter-rouge">CreateSubscriptionAsync</code> → Se encarga de crear la subscripción dentro del APIM una vez que Stripe nos reporte que se ha validado y registrado correctamente el pago.</li>
  <li><code class="language-plaintext highlighter-rouge">GetProductAsync</code> → Obtiene información de un producto desde el APIM empleado para propósitos de subscripción.</li>
  <li><code class="language-plaintext highlighter-rouge">SuspendSubscriptionAsync</code> → Suspende una subscripción, habitualmente por motivos de impago. A diferencia de una cancelación, si se paga la deuda, la subscripción se reactivaría.</li>
  <li><code class="language-plaintext highlighter-rouge">ValidateRequest</code> → La explico a continuación.</li>
</ul>

<p>Un de los primeros métodos a implementar es el <code class="language-plaintext highlighter-rouge">ValidateRequest</code>, ya que cada vez que el APIM llama a una aplicación externa en la delegación de servicios, es fundamental verificar la validez de la llamada. Esta validación se hace empleando el algoritmo de HMAC para verificación de firmas con una encriptación <code class="language-plaintext highlighter-rouge">SHA-512</code>.</p>

<p>Así, la implementación del método <code class="language-plaintext highlighter-rouge">ValidateRequest</code> sería la siguiente:</p>

<figure class="align-center"><a href="https://codertectura.com//images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/10.png" target="_blank" rel="noopener" data-lity="">
            <img src="/images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/10.png" alt="" class=" " style="" />
        </a><figcaption>
        Haz click para ver la imagen más grande.

    </figcaption></figure>

<p>La clase <code class="language-plaintext highlighter-rouge">HMACSHA512</code> la proporciona .NET 6 en <code class="language-plaintext highlighter-rouge">System.Security.Cryptography</code>.</p>

<p>Como puedes ver, se emplea desde la configuración el valor de la opción <code class="language-plaintext highlighter-rouge">DelegationValidationKey</code>, que es utilizada para verificar que efectivamente la petición llega desde el APIM y que un potencial atacante no ha manipulado la petición. Esto importante porque para bien o para mal todas las peticiones del APIM hacia la aplicación sobre la que delega operaciones envía los parámetros por URL (en forma de query strings) con lo cual son “<em>capturables</em>” por un potencial atacante.</p>

<p>Dependiendo de cada operación que envíe desde el APIM a la aplicación externa de delegación de servicios, se debe validar un conjunto específico de parámetros, los cuales podemos consultar en la documentación oficial de Microsoft aquí: <a href="https://learn.microsoft.com/es-es/azure/api-management/api-management-howto-setup-delegation#create-your-delegation-endpoint-1" target="_blank">https://learn.microsoft.com/es-es/azure/api-management/api-management-howto-setup-delegation#create-your-delegation-endpoint-1</a>.</p>

<p>En el caso de nuestro código tenemos las siguientes implementaciones:</p>

<figure class="align-center"><a href="https://codertectura.com//images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/11.png" target="_blank" rel="noopener" data-lity="">
            <img src="/images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/11.png" alt="" class=" " style="" />
        </a><figcaption>
        Haz click para ver la imagen más grande.

    </figcaption></figure>

<p>Para cada situación de delegación necesitaremos controladores y pantallas concretas para atenderlas.</p>

<p>Cubrir todo el código que las implementa en una publicación como esta puede ser algo extenso (casi un libro 🤓), con lo cual os indicaré para qué es cada controlador y pantalla en la aplicación web y os invito a que os clonéis el repo para revisarla y adaptarla a vuestras necesidades.</p>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">Index</code> → Como os comentaba es el controlador que recibe las peticiones desde el APIM y las enruta al controlador o pantalla más conveniente para atenderla.</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">Unsubscribe</code> → Se encarga de cancelar una subscripción en el APIM y en Stripe.</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">Subscribe</code> → Se encarga de capturar la información para la subscripción de parte del usuario. En nuestro caso, sólo nos interesa el nombre de la subscripción. Al final de procesamiento, nos redirige el procesamiento a StripeCheckout.</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">StripeCheckout</code> → Se encarga de configurar el cliente JavaScript de Stripe para realizar la redirección a la pantalla que capturará la información de la tarjeta de crédito. Recordemos que como ni Azure ni el APIM están certificadas en certificadas en <a href="https://www.pcisecuritystandards.org/" target="_blank">PCI-DSS</a>, la pantalla que se muestra para obtener esta información del cliente está en la infraestructura y contexto de ejecución de Stripe, por lo cual Stripe debe eventualmente conocer a donde redirigir si todo ha salido bien. Para ello, la pantalla de <code class="language-plaintext highlighter-rouge">StripeCheckout</code> primero hace un POST a su Controller inicializar una sesión de pago con Stripe. El identificador de esa sesión es lo que se usará en el código del cliente para renderizar la pantalla de Stripe en el navegador, fuera de Azure y nuestra web app, y dentro e Stripe. Uno de esos datos es la dirección donde se está ejecutando nuestra aplicación, la cual tenemos que capturar desde el navegador pues nuestro aplicativo está en Docker y de hacerlo en el lado servidor nos retornaría siempre <code class="language-plaintext highlighter-rouge">localhost</code>.</p>
  </li>
</ul>

<figure class="align-center"><a href="https://codertectura.com//images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/12.png" target="_blank" rel="noopener" data-lity="">
            <img src="/images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/12.png" alt="" class=" " style="" />
        </a><figcaption>
        Haz click para ver la imagen más grande.

    </figcaption></figure>

<p>Al recibir el POST, una de las primeras cosas que se hacen es construir las URLs para escenarios de cancelación y éxito del pago (ver líneas 12 a 32 en la siguiente imagen), las cuales necesitan una URL de retorno que se obtiene del lado cliente como se muestra en la imagen anterior, en la línea morada dentro del recuadro verde.</p>

<figure class="align-center"><a href="https://codertectura.com//images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/13.png" target="_blank" rel="noopener" data-lity="">
            <img src="/images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/13.png" alt="" class=" " style="" />
        </a><figcaption>
        Haz click para ver la imagen más grande.

    </figcaption></figure>

<p>Con esta información (líneas 36 a 75) se crea una sesión de Stripe.</p>

<p>Nótese como en las líneas 55 a 73 se especifica el precio a pagar, así como las cantidades. El precio es obtenido desde Stripe empleado una instancia de su clase <code class="language-plaintext highlighter-rouge">PriceService</code> e identificando el producto con el identificador de producto del APIM. Esto es posible porque cuando inicializamos Stripe con el script de PowerShell tomamos en cuenta la configuración de productos del APIM, de la cual, entre otras cosas, se toma el identificador del producto para emplearlo tal cual como identificador en Stripe.</p>

<p>Creada la sesión de Stripe, basta con retornar su identificador como resultado del POST para que el código del lado del cliente en su navegador (cuadro naranja dos imágenes más arriba) haga la redirección.</p>

<ul>
  <li>
    <p><code class="language-plaintext highlighter-rouge">PaymentCancelled</code> → Notifica al usuario que ha habido algún fallo con el pago desde Stripe y le da la oportunidad de reintentar.</p>
  </li>
  <li>
    <p><code class="language-plaintext highlighter-rouge">PaymentSucceeded</code> → Notifica al usuario que todo ha salido bien con el pago, y que su subscripción estará pronto disponible, redirigiéndole al Developers Portal.</p>
  </li>
</ul>

<p>Ahora bien, una vez que Stripe ha procesado el pago necesita comunicar al APIM que todo ha ido bien para que se realizan las acciones necesarias para que la subscripción esté disponible para el usuario y sea visible en el Developer Portal.</p>

<p>Esta integración con Stripe requiere de un webhook, al cual Stripe enviará notificaciones sobre cambios en las subscripciones.</p>

<p>En concreto nos interesan tres eventos:</p>

<ul>
  <li>
    <p>Subscripción creada representada por el evento <code class="language-plaintext highlighter-rouge">CUSTOMER.SUBSCRIPTION.CREATED</code>, es enviado por Stripe una vez la
información del pago se ha validado y registrado correctamente para que el APIM pueda crear la subscripción.</p>
  </li>
  <li>
    <p>Subscripción actualizada representada por el evento <code class="language-plaintext highlighter-rouge">CUSTOMER.SUBSCRIPTION.UPDATED</code> es enviada por Stripe cuando algo pasa con la subscripción, por ejemplo, que no se ha podido pagar y que por lo tanto debería ser suspendida.</p>
  </li>
  <li>
    <p>Subscripción cancelada representada por ele vento <code class="language-plaintext highlighter-rouge">CUSTOMER.SUBSCRIPTION.DELETED</code> es enviada por Stripe si la subscripción es borrada desde el propio portal de Stripe para que sea cancelada en el APIM.</p>
  </li>
</ul>

<p>El <em>webhook</em> está implementado por un <code class="language-plaintext highlighter-rouge">Controller</code> llamado <code class="language-plaintext highlighter-rouge">StripeWebhook</code>, y utiliza los métodos de la interface <code class="language-plaintext highlighter-rouge">IApimService</code> para atender a cada evento como según corresponda.</p>

<p>En caso de fallo de comunicación, Stripe se encargará de constantemente llamar al <em>webhook</em> para asegurar que las acciones necesarias se lleven a cabo. Por su parte, el <em>webhook</em> puede notificaría a Stripe que todo está en orden simplemente retornando un valor <em>booleano</em> a <code class="language-plaintext highlighter-rouge">true</code>.</p>

<h3 id="reportando-consumo-desde-azure-api-managementa-stripe-para-su-facturación">Reportando consumo desde Azure API Management<br />a Stripe para su facturación</h3>

<p>Cuando los usuarios realizan llamadas a las APIs de cada Producto dentro del Azure API Management (APIM) deben hacer uso de una clave de subscripción que les identifica. Gracias a esta clave, una base de datos interna del APIM registra las llamadas (tanto totales, como segregadas por exitosas o fallidas) para que puedan ser reportadas en un informe que se puede utilizar para calcular cuánto cobrar a dichos usuarios.</p>

<p>En esta demo usaremos una Azure Function para obtener esta información, procesarla y enviarla a Stripe.</p>

<p>La Azure Function se debe ejecutar periódicamente. Para la demo tengo puesto que se ejecute cada minuto, pero esto es un overkill en toda regla. Lo suyo sería que se ejecute cada 24 horas dentro de una ventana de tiempo que se considere “apropiada”.</p>

<p>A la hora de recuperar información, hay que decidir que plataforma (el APIM o Stripe)  consideramos como el maestro de los datos. En mi caso, para la demo he considerado que Stripe lleva la voz cantante en cuanto al estado de las subscripciones, razón por la cual comienza la ejecución de la Azure Function recuperando las subscripciones activas desde Stripe. No obstante, otra alternativa es obtener las subscripciones activas desde el APIM y luego consultar Stripe. Elegí este enfoque porque es el que realiza menos llamadas.</p>

<figure class="align-center"><a href="https://codertectura.com//images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/14.png" target="_blank" rel="noopener" data-lity="">
            <img src="/images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/14.png" alt="" class=" " style="" />
        </a><figcaption>
        Haz click para ver la imagen más grande.

    </figcaption></figure>

<p>Una vez que tenemos las subscripciones activas desde Stripe, iteramos sobre éstas hasta que ya no queden subscripciones por procesar.</p>

<p>Por cada subscripción, se obtiene el identificador de esta en el APIM (el cual es  almacenado como metadata en Stripe al crear la subscripción). También se obtiene de la metadata un valor identificado como <code class="language-plaintext highlighter-rouge">last-usage-update</code> el cual es utilizado para calcular que no existan espacios de tiempo entre los usos habidos de un API y sus usos reportados, tal que no se pierda datos de facturación. Esto lo hago entre las líneas 26 y 39 de la  siguiente imagen, aunque la mayoría de las veces es un código que rara vez se ejecuta; no  obstante, es importante entender que el “tema del dinero” es delicado y que tenemos que  considerar las casuísticas límite (o borde) para asegurar que no se pierde facturación.</p>

<figure class="align-center"><a href="https://codertectura.com//images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/15.png" target="_blank" rel="noopener" data-lity="">
            <img src="/images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/15.png" alt="" class=" " style="" />
        </a><figcaption>
        Haz click para ver la imagen más grande.

    </figcaption></figure>

<p>Nótese que en la línea 30 estamos obteniendo el reporte de uso de la subscripción en un  período específico. El método <code class="language-plaintext highlighter-rouge">GetUsageReportAsync</code> está implementado de forma  privada dentro de la función de la siguiente manera:</p>

<figure class="align-center"><a href="https://codertectura.com//images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/16.png" target="_blank" rel="noopener" data-lity="">
            <img src="/images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/16.png" alt="" class=" " style="" />
        </a><figcaption>
        Haz click para ver la imagen más grande.

    </figcaption></figure>

<p>Los filtros usados para obtener el reporte de consumo usan notación de OData.</p>

<p>Luego tenemos el método privado <code class="language-plaintext highlighter-rouge">ProcessReportAsync</code> que toma el reporte de uso y lo procesa de forma tal que se envía información a Stripe sobre el consumo. Su  implementación es la siguiente:</p>

<figure class="align-center"><a href="https://codertectura.com//images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/17.png" target="_blank" rel="noopener" data-lity="">
            <img src="/images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/17.png" alt="" class=" " style="" />
        </a><figcaption>
        Haz click para ver la imagen más grande.

    </figcaption></figure>

<p>El código determina si existen datos de uso en el reporte obtenido desde el APIM. De haberlos, se crea un registro de consumo con la cantidad de unidades reportadas y en una fecha específica. Luego se actualiza la subscripción en Stripe con un nuevo valor para la metadata identificada como <code class="language-plaintext highlighter-rouge">last-usage-update</code> para asegurarnos como mencionamos antes que no se pierden datos de facturación.</p>

<p>Y esto es todo lo que hace la Azure Function. Gracias al API de gestión del APIM y a cómo éste almacena en una base de datos interna la información sobre el uso de las APIs, podemos fácilmente informar al proveedor de pagos (en este caso Stripe) de forma periódica cuántas llamadas se han realizado para que las conserve y cobre a los usuarios cuando corresponda según el modelo de negocio configurado.</p>

<p>Por último, puede ser interesante destacar que en una implementación “más real” que la de esta demo, cada acción que realiza esta Azure Function esté repartida entre varias Azure Functions, conectadas entre sí mediante un mecanismo que siga el patrón de diseño «<a href="https://microservices.io/patterns/data/transactional-outbox.html" target="_blank">Transactional Outbox</a>», el cual es relativamente sencillo de implementar utilizando Cosmos DB y su funcionalidad de «<a href="https://learn.microsoft.com/en-us/azure/cosmos-db/change-feed" target="_blank">Change Feed</a>» combinada con la capacidad de «<a href="https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/transactional-batch?tabs=dotnet" target="_blank">Transactional Batches</a>», aunque eso es tema para otra conversación y otra publicación 😉</p>

<p><strong>¡Muchas gracias por leerme y espero que os sea de utilidad!</strong></p>]]></content><author><name>Rodrigo Liberoff</name><email>rliberoff@outlook.com</email></author><category term="Tutorial" /><category term="Azure" /><category term="C#" /><category term="API" /><category term="APIM" /><category term="Azure API Management" /><category term="Inteligencia Artificial" /><summary type="html"><![CDATA[En esta serie de tres publicaciones, voy a ir mostrando los detalles técnicos que impartí en la pasada Global Azure Spain 2023 en Madrid sobre monetización de APIs empleando el Azure API Management en una arquitectura totalmente «Cloud Native», y que será de muchísima utilidad para capitalizar nuestras APIs, sobre todo las de Inteligencia Artificial.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://codertectura.com//images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/header.webp" /><media:content medium="image" url="https://codertectura.com//images/2023-05-31-moneitzar-apis-inteligencia-artificial-parte-3/header.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Cómo monetizar nuestras APIs de Inteligencia Artificial con Azure API Management - Parte 2: Implementación</title><link href="https://codertectura.com//posts/apis-monetizacion-parte-2" rel="alternate" type="text/html" title="Cómo monetizar nuestras APIs de Inteligencia Artificial con Azure API Management - Parte 2: Implementación" /><published>2023-05-30T00:00:00+00:00</published><updated>2023-10-29T00:00:00+00:00</updated><id>https://codertectura.com//posts/moneitzar-apis-inteligencia-artificial-parte-2</id><content type="html" xml:base="https://codertectura.com//posts/apis-monetizacion-parte-2"><![CDATA[<div class="notice--info" style="font-size: small; font-weight: bold;">
 
<p>Artítculo publicado originalmente en el blog <a href="https://blogs.encamina.com/piensa-en-software-desarrolla-en-colores/como-monetizar-nuestras-apis-de-inteligencia-artificial-con-el-azure-api-management-2a-parte/" target="_blank">«Piensa en software, desarrolla en colores»</a> de <a href="https://www.encamina.com/" target="_blank">ENCAMINA</a>.</p>

</div>

<div class="notice--success" style="font-size: medium;">
  
<p>Este es la primera parte de una serie de trest publicaciones:</p>

<ul>
  <li><a href="/posts/apis-monetizacion-parte-1">Parte 1: Introducción</a></li>
  <li><a href="/posts/apis-monetizacion-parte-3">Parte 3: Delegación en el APIM</a></li>
</ul>

</div>

<p>La implementación de una arquitectura efectiva es fundamental para gestionar y monetizar APIs de Inteligencia Artifical de manera eficiente. En este artículo, nos centraremos en el componente central de esta arquitectura y también exploraremos la concepción del modelo de negocio y la inicialización del proveedor de pagos, utilizando Stripe (<a href="https://stripe.com/es" target="_blank">https://stripe.com/es</a>).</p>

<h3 id="arquitectura-de-la-solución">Arquitectura de la solución</h3>

<p>El componente central de la arquitectura es el Azure API Management, un servicio extraordinario de Azure que permite la integración y transformación de APIs de origen heterogéneo en un único componente que centraliza su gestión y su gobierno. Habitualmente, el Azure API Management se abrevia como APIM.</p>

<p>Con el APIM podrás afrontar, entre otros, los siguientes desafíos:</p>

<ul>
  <li>
    <p>Abordar la diversidad y complejidad en las abstracciones de los <em>backends</em> (la parte que implementa la lógica de negocio de las APIs), así como la complejidad que puedan imponer los consumidores en las formas en que requieren o solicitan acceder a los APIs.</p>
  </li>
  <li>
    <p>Exponer de forma segura los servicios hospedados dentro y fuera de Azure como APIs.</p>
  </li>
  <li>
    <p>Facilitar y propiciar el descubrimiento y consumo de las APIs por parte de agentes internos y externos a la organización.</p>
  </li>
  <li>
    <p>Incrementar la protección de las APIs, acelerar significativamente su implementación y mejora, así como la “observabilidad” de su desempeño, uso y funcionamiento.</p>
  </li>
  <li>
    <p>Flexibilizar y gestionar el acceso controlado a las APIs, así como medir su consumo para acciones de monetización.</p>
  </li>
</ul>

<p>En ese sentido, la forma en alto nivel que tendrá la arquitectura que implementaremos es la siguiente:</p>

<figure class="align-center"><a href="https://codertectura.com//images/2023-05-30-moneitzar-apis-inteligencia-artificial-parte-2/1.png" target="_blank" rel="noopener" data-lity="">
            <img src="/images/2023-05-30-moneitzar-apis-inteligencia-artificial-parte-2/1.png" alt="" class="image-border " style="" />
        </a><figcaption>
        Arquitectura en alto nivel para la monetización de APIs con el Azure API Management.<br />Haz click para ver la imagen más grande.

    </figcaption></figure>

<p>En esta arquitectura contaremos con los siguientes componentes:</p>

<ul>
  <li>
    <p>Un Azure API Management (APIM) con el Developers Portal.</p>
  </li>
  <li>
    <p>Un Azure Container Application (ACA) con una aplicación de Docker desarrollada en .NET 6 y programada con C# para gestionar el proceso de subscripciones a las APIs y la integración con el sistema de pagos.</p>
  </li>
  <li>
    <p>El sistema de pagos representado por Stripe.</p>
  </li>
  <li>
    <p>Una Azure Function que se encargará de reportar periódicamente los consumos de APIs de tus clientes al sistema de pagos.</p>
  </li>
  <li>
    <p>Servicios generalistas de Azure tales como un Azure Container Registry (ACR)  para mantener las imágenes de Docker a desplegar en el ACA, un Azure Storage Account para preservar archivos que necesitaremos en nuestra solución y una Azure Applications Insights integrado a un Azure Log Space para la conservación de trazas generadas por el APIM y el ACA.</p>
  </li>
</ul>

<h3 id="concibiendo-el-modelo-de-negocio">Concibiendo el modelo de negocio</h3>

<p>Concebir un modelo de negocio nunca es una tarea sencilla. Si tu equipo trabaja bajo un  modelo de agilidad (digamos Scrum) es perfectamente normal que el concebir y establecer  el modelo de negocio para tus APIs tome fácilmente de tres a cuatro Sprints.</p>

<p>Tras definir el modelo de negocio es probable que las siguientes tareas sean desarrollar una aplicación para su gestión que se integre con el Azure API Management (APIM) mediante su <a href="https://learn.microsoft.com/en-us/rest/api/apimanagement/" target="_blank">API REST de gestión</a> para crear y actualizar los elementos del modelo de negocio, siendo de los principales los Productos y las APIs vinculadas a cada uno.</p>

<p>Por temas de espacio de tiempo, crear tal tipo de aplicación se escapa al propósito de esta publicación, por lo cual simularemos el modelo de negocio diseñado empleando un archivo JSON que podrás encontrar en el repo de GitHub aquí 👉 <a href="https://github.com/rliberoff/Global-Azure-Spain-2023-API-Monetization/blob/main/businessModel/monetizationModels.json" target="_blank">https://github.com/rliberoff/Global-Azure-Spain-2023-API-Monetization/blob/main/businessModel/monetizationModels.json</a>.</p>

<p>Como tal, el fichero se ve así (lo pongo en un GIF animado porque es muy largo):</p>

<figure class="align-center"><img src="/images/2023-05-30-moneitzar-apis-inteligencia-artificial-parte-2/2.gif" alt="" class="image-border " style="width: 650px;" /><figcaption>
        Extracto del JSON que representa (simula) el modelo de negocio.

    </figcaption></figure>

<p>Este archivo JSON lo mantendremos y accederemos desde un contenedor de Blobs en el Azure Storage Account.</p>

<p>Así mismo, el equivalente en el APIM a este modelo de negocio fue creado manualmente y se ve de la siguiente forma:</p>

<figure class="align-center"><a href="https://codertectura.com//images/2023-05-30-moneitzar-apis-inteligencia-artificial-parte-2/3.png" target="_blank" rel="noopener" data-lity="">
            <img src="/images/2023-05-30-moneitzar-apis-inteligencia-artificial-parte-2/3.png" alt="" class="image-border " style="" />
        </a><figcaption>
        Como se ve el modelo de negocio en el Azure API Management, en el apartado de «Productos».<br />Haz click para ver la imagen más grande.

    </figcaption></figure>

<h3 id="inicializando-el-proveedor-de-pagos">Inicializando el proveedor de pagos</h3>

<p>Como he mencionado antes, usaremos Stripe como mecanismo de pagos, siendo las razones de mi elección las siguientes:</p>

<ul>
  <li>
    <p>Es un proveedor de pagos, con lo cual no nos hace falta implementar por nuestra cuenta los sistemas que tendrían que calcular cuánto cobrar a los consumidores periódicamente, solamente tenemos que implementar el mecanismo de reportar el consumo para que le sea cobrado a los clientes.</p>
  </li>
  <li>
    <p>Permite tener una cuenta de desarrollador completamente gratis y sin restricciones, proporcionando tarjetas de crédito para pruebas.</p>
  </li>
  <li>
    <p>Tiene un API de implementación simplemente exquisito, con ejemplos muy buenos y completos para una gran variedad de lenguajes de programación como C#, TypeScript (y JavaScript), Python, Java, y más.</p>
  </li>
  <li>
    <p>Las librerías de integración se actualizan constante y regularmente.</p>
  </li>
  <li>
    <p>Proporciona un CLI que nos permite integrar capacidades de gestión automatizadas para tares de DevOps y para integraciones necesarias durante flujos de integración y despliegue continuos.</p>
  </li>
  <li>
    <p>Permite tener una representación de nuestro modelo de negocio, con lo cual podemos definir productos que se facturen en diferentes tiempos (semanales, mensuales, trimestrales).</p>
  </li>
</ul>

<p>El único defecto que le veo a Stripe es que, de la variedad de sistemas ofrecidos en el mercado, está entre los más costosos.</p>

<p>Partiendo de que ya cuentas con una cuenta de desarrollador de Stripe, lo que haremos es ejecutar un <em>script</em> de PowerShell para inicializar el modelo de negocio dentro de Stripe. También la usaremos para inicializar un <em>webhook</em> que necesitaremos para la comunicación asíncrona entre Stripe y el Azure API Management.</p>

<p>Para poder ejecutar este script, necesitamos crear una API Key en Stripe con permisos específicos para la personalización de productos y servicios. Para ello:</p>

<ol>
  <li>Vamos a nuestra cuenta de Stripe y accedemos al dashboard de desarrolladores.</li>
  <li>Elegimos la opción de “API Keys” del menú superior.</li>
  <li>En la sección de “Restricted Keys” le damos al botón de “Create restricted key”.</li>
  <li>Le ponemos un nombre a la API Key que estamos creando, por ejemplo «Init Stripe».</li>
  <li>En los permisos, otorgamos permisos de lectura y escritura a los aspectos de Prices, Products y Webhook Endpoints.</li>
</ol>

<p>Una vez que tenemos este API Key, vamos a ver qué hace este <em>script</em>, el cual encontrarás completo en el repo de GitHub aquí 👉 <a href="https://github.com/rliberoff/Global-Azure-Spain-2023-API-Monetization/blob/main/scripts/stripeInitialisation.ps1" target="_blank">https://github.com/rliberoff/Global-Azure-Spain-2023-API-Monetization/blob/main/scripts/stripeInitialisation.ps1</a>.</p>

<p>Lo primero es que el <em>script</em> necesita cinco parámetros:</p>

<ul>
  <li>Un Stripe API Key que creamos anteriormente llamado «Init Stripe».</li>
  <li>La URL para el webhook. Esta URL debe ser la URL del Azure Applicacion Container (ACA) donde desplegaremos la aplicación de gestión de las subscripciones, seguido del valor webhook/stripe.</li>
  <li>La URL al archivo JSON con el modelo de monetización. Al estar desplegado en un contenedor de Blobs, podemos obtener una URL pública para acceder a este recurso directamente.</li>
  <li>La URL del gateway del Azure API Management (APIM).</li>
</ul>

<figure class="align-center"><a href="https://codertectura.com//images/2023-05-30-moneitzar-apis-inteligencia-artificial-parte-2/4.png" target="_blank" rel="noopener" data-lity="">
            <img src="/images/2023-05-30-moneitzar-apis-inteligencia-artificial-parte-2/4.png" alt="" class="image-border " style="" />
        </a><figcaption>
        Ubicación de la URL del Azure API Management Gateway.<br />Haz click para ver la imagen más grande.

    </figcaption></figure>

<ul>
  <li>Una de las claves de subscripción del APIM, siendo la recomendada la que ya trae definida al provisionarse el recurso:</li>
</ul>

<figure class="align-center"><a href="https://codertectura.com//images/2023-05-30-moneitzar-apis-inteligencia-artificial-parte-2/5.png" target="_blank" rel="noopener" data-lity="">
            <img src="/images/2023-05-30-moneitzar-apis-inteligencia-artificial-parte-2/5.png" alt="" class="image-border " style="" />
        </a><figcaption>
        Ubicación de las claves de subscripción en el Azure API Management.<br />Haz click para ver la imagen más grande.

    </figcaption></figure>

<p>La llamada al <em>script</em> se ve de la siguiente forma:</p>

<figure class="align-center"><a href="https://codertectura.com//images/2023-05-30-moneitzar-apis-inteligencia-artificial-parte-2/6.png" target="_blank" rel="noopener" data-lity="">
            <img src="/images/2023-05-30-moneitzar-apis-inteligencia-artificial-parte-2/6.png" alt="" class=" " style="" />
        </a><figcaption>
        Haz click para ver la imagen más grande.

    </figcaption></figure>

<p>La primera parte del <em>script</em> se encarga de instalar la versión más reciente del CLI de Stripe:</p>

<figure class="align-center"><a href="https://codertectura.com//images/2023-05-30-moneitzar-apis-inteligencia-artificial-parte-2/7.png" target="_blank" rel="noopener" data-lity="">
            <img src="/images/2023-05-30-moneitzar-apis-inteligencia-artificial-parte-2/7.png" alt="" class=" " style="" />
        </a><figcaption>
        Primera parte del <em>script</em> de PowerShell de configuración de Stripe que se encarga de capturar los parámetros y de instalar el CLI de Stripe.<br />Haz click para ver la imagen más grande.

    </figcaption></figure>

<p>La siguiente parte del <em>script</em> hace dos cosas, por un lado, descarga el JSON con la  definición del modelo de negocio desde el contenedor de Blobs en el Azure Storage Account, y por otro se trae los productos definidos en el APIM a través del API REST de gestión.</p>

<figure class="align-center"><a href="https://codertectura.com//images/2023-05-30-moneitzar-apis-inteligencia-artificial-parte-2/8.png" target="_blank" rel="noopener" data-lity="">
            <img src="/images/2023-05-30-moneitzar-apis-inteligencia-artificial-parte-2/8.png" alt="" class=" " style="" />
        </a><figcaption>
        Haz click para ver la imagen más grande.

    </figcaption></figure>

<p>Hago esto para poder asegurarme de que el producto que voy a crear en Stripe efectivamente existe configurado en el APIM y que concuerden los IDs de los mismos entre ambas plataformas, ya que será a través de dichos identificadores que, como veremos más adelante, se identificarán los consumos para generar la facturación y efectivamente monetizar nuestras APIs.</p>

<p>La siguiente parte del código es un poco más larga y os invito a leerla directamente del repo, y es la que se encarga de usar el CLI de Stripe para de forma recursiva hacer llamadas para crear los productos, sus características, sus precios y su frecuencia de facturación. Aquí es donde podríamos definir que un producto sea facturado semanal, mensual, trimestral, semestral o anualmente.</p>

<p>Por último, creamos el <em>webhook</em> y recuperamos su secreto para poder emplearlo como parámetro de configuración en la aplicación que desplegaremos en el Azure Container Application (ACA).</p>

<figure class="align-center"><a href="https://codertectura.com//images/2023-05-30-moneitzar-apis-inteligencia-artificial-parte-2/9.png" target="_blank" rel="noopener" data-lity="">
            <img src="/images/2023-05-30-moneitzar-apis-inteligencia-artificial-parte-2/9.png" alt="" class=" " style="" />
        </a><figcaption>
        Haz click para ver la imagen más grande.

    </figcaption></figure>

<p>Tras ejecutar este <em>script</em>, si entramos en nuestra cuenta de Stripe veremos como los productos estarán configurados:</p>

<figure class="align-center"><a href="https://codertectura.com//images/2023-05-30-moneitzar-apis-inteligencia-artificial-parte-2/10.png" target="_blank" rel="noopener" data-lity="">
            <img src="/images/2023-05-30-moneitzar-apis-inteligencia-artificial-parte-2/10.png" alt="" class="image-border " style="" />
        </a><figcaption>
        Haz click para ver la imagen más grande.

    </figcaption></figure>

<p>En la próxima publicación abordaremos la delegación de capacidades en Azure API Management, incluyendo la gestión de subscripciones y la integración con Stripe para la facturación 👉 <a href="/post/apis-monetizacion-parte-3">Parte 3</a>.</p>]]></content><author><name>Rodrigo Liberoff</name><email>rliberoff@outlook.com</email></author><category term="Tutorial" /><category term="Azure" /><category term="C#" /><category term="API" /><category term="APIM" /><category term="Azure API Management" /><category term="Inteligencia Artificial" /><summary type="html"><![CDATA[En esta serie de tres publicaciones, voy a ir mostrando los detalles técnicos que impartí en la pasada Global Azure Spain 2023 en Madrid sobre monetización de APIs empleando el Azure API Management en una arquitectura totalmente «Cloud Native», y que será de muchísima utilidad para capitalizar nuestras APIs, sobre todo las de Inteligencia Artificial.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://codertectura.com//images/2023-05-30-moneitzar-apis-inteligencia-artificial-parte-2/header.png" /><media:content medium="image" url="https://codertectura.com//images/2023-05-30-moneitzar-apis-inteligencia-artificial-parte-2/header.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>