<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: igcodinap</title>
    <description>The latest articles on DEV Community by igcodinap (@igcodinap).</description>
    <link>https://dev.to/igcodinap</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F227033%2F720053a1-4ceb-4eb7-92f9-f7de7511d80e.jpeg</url>
      <title>DEV Community: igcodinap</title>
      <link>https://dev.to/igcodinap</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/igcodinap"/>
    <language>en</language>
    <item>
      <title>go-eval: la pieza que faltaba para probar agentes en Go</title>
      <dc:creator>igcodinap</dc:creator>
      <pubDate>Wed, 29 Apr 2026 00:41:59 +0000</pubDate>
      <link>https://dev.to/igcodinap/go-eval-la-pieza-que-faltaba-para-probar-agentes-en-go-1fgc</link>
      <guid>https://dev.to/igcodinap/go-eval-la-pieza-que-faltaba-para-probar-agentes-en-go-1fgc</guid>
      <description>&lt;p&gt;Hace un tiempo empecé a sentir una incomodidad rara construyendo aplicaciones con LLMs en Go.&lt;/p&gt;

&lt;p&gt;Go tenía casi todo lo que quería para construir software agéntico: concurrencia simple, deploys predecibles, binarios pequeños, buen soporte para HTTP, workers, colas, bases de datos, observabilidad y testing. Pero justo en la parte más importante aparecía un vacío:&lt;/p&gt;

&lt;p&gt;¿Cómo probamos que un agente está respondiendo bien?&lt;/p&gt;

&lt;p&gt;No si compila.&lt;/p&gt;

&lt;p&gt;No si devuelve un &lt;code&gt;200&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;No si el JSON tiene una llave llamada &lt;code&gt;answer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Sino si la respuesta es fiel al contexto, si no inventó hechos, si usó bien los documentos recuperados, si siguió la rúbrica, si eligió la herramienta correcta, si bajó de calidad después de cambiar un prompt.&lt;/p&gt;

&lt;p&gt;En Python existen varias herramientas maduras para evaluación de LLMs. En Go, muchas veces terminamos con scripts sueltos, checks de strings, distancia Levenshtein, planillas, o una persona mirando respuestas a mano.&lt;/p&gt;

&lt;p&gt;Eso no escala.&lt;/p&gt;

&lt;p&gt;Y, peor aún, saca la evaluación del lugar donde los gophers ya sabemos trabajar: &lt;code&gt;go test&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  El problema: los agentes no fallan como una API tradicional
&lt;/h2&gt;

&lt;p&gt;Una API tradicional suele fallar de formas relativamente claras:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;devuelve un error&lt;/li&gt;
&lt;li&gt;rompe un contrato&lt;/li&gt;
&lt;li&gt;persiste mal un dato&lt;/li&gt;
&lt;li&gt;se demora demasiado&lt;/li&gt;
&lt;li&gt;responde un código HTTP incorrecto&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Para eso Go ya tiene un flujo excelente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;test&lt;/span&gt; ./...
go &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-race&lt;/span&gt; ./...
go &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-bench&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; ./...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pero una aplicación con LLMs puede fallar de formas más suaves y más peligrosas.&lt;/p&gt;

&lt;p&gt;Puede responder con mucha confianza algo falso.&lt;/p&gt;

&lt;p&gt;Puede ignorar el contexto que recuperó desde la base vectorial.&lt;/p&gt;

&lt;p&gt;Puede contestar algo razonable, pero no responder la pregunta del usuario.&lt;/p&gt;

&lt;p&gt;Puede devolver JSON válido, pero semánticamente inútil.&lt;/p&gt;

&lt;p&gt;Puede mejorar en un caso y empeorar en otro después de cambiar el system prompt.&lt;/p&gt;

&lt;p&gt;El problema no es solo técnico. Es cultural: si no podemos escribir pruebas repetibles sobre estos comportamientos, terminamos aceptando que la calidad de un agente se mide "probando un rato en el chat".&lt;/p&gt;

&lt;p&gt;Eso está bien para una demo.&lt;/p&gt;

&lt;p&gt;No está bien para software serio.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué es go-eval
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;go-eval&lt;/code&gt; es una librería de evaluación de LLMs para Go, pensada para correr dentro de &lt;code&gt;go test&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;La idea es simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go test
  └── Runner
        ├── Case
        ├── Metric
        └── Judge
              └── LLM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Un &lt;code&gt;Case&lt;/code&gt; describe lo que queremos evaluar.&lt;/p&gt;

&lt;p&gt;Un &lt;code&gt;Metric&lt;/code&gt; define cómo se mide la calidad.&lt;/p&gt;

&lt;p&gt;Un &lt;code&gt;Judge&lt;/code&gt; es la abstracción sobre el modelo que juzga.&lt;/p&gt;

&lt;p&gt;Un &lt;code&gt;Runner&lt;/code&gt; conecta todo con &lt;code&gt;testing.TB&lt;/code&gt;, para que los resultados vivan en el mismo flujo que el resto de nuestros tests.&lt;/p&gt;

&lt;p&gt;El core no depende de OpenAI, ni de una plataforma externa, ni de un dashboard hosted. Es Go normal, con interfaces pequeñas.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Judge&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JudgeResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Metric&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="n"&gt;Judge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="n"&gt;Case&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Case&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Input&lt;/span&gt;    &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Output&lt;/span&gt;   &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Expected&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Context&lt;/span&gt;  &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Metadata&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto es intencional. Si queremos que Go sea un lenguaje natural para aplicaciones agénticas, las evaluaciones no pueden sentirse como una herramienta ajena al ecosistema.&lt;/p&gt;

&lt;p&gt;Tienen que sentirse como Go.&lt;/p&gt;

&lt;h2&gt;
  
  
  El primer principio: evaluaciones opt-in
&lt;/h2&gt;

&lt;p&gt;Un test con LLMs puede ser lento, costoso y no siempre determinístico. Por eso &lt;code&gt;go-eval&lt;/code&gt; no corre evaluaciones por accidente.&lt;/p&gt;

&lt;p&gt;Todas las ejecuciones de &lt;code&gt;Runner.Run&lt;/code&gt; están protegidas por &lt;code&gt;GOEVAL&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;test&lt;/span&gt; ./...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Con &lt;code&gt;GOEVAL&lt;/code&gt; apagado, los evals se saltan.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;GOEVAL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 go &lt;span class="nb"&gt;test&lt;/span&gt; ./...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Con &lt;code&gt;GOEVAL=1&lt;/code&gt;, se ejecutan.&lt;/p&gt;

&lt;p&gt;Este detalle parece pequeño, pero cambia la ergonomía. Puedes commitear tus evals junto al código, correr CI rápido por defecto, y activar evaluaciones semánticas cuando quieres validar calidad, comparar modelos o preparar un release.&lt;/p&gt;

&lt;h2&gt;
  
  
  Un ejemplo: evaluando una respuesta RAG
&lt;/h2&gt;

&lt;p&gt;Supongamos que tenemos una aplicación RAG. El usuario pregunta algo, recuperamos documentos y generamos una respuesta.&lt;/p&gt;

&lt;p&gt;Con tests tradicionales podemos verificar que el pipeline no explote. Pero eso no nos dice si la respuesta está fundada en el contexto.&lt;/p&gt;

&lt;p&gt;Con &lt;code&gt;go-eval&lt;/code&gt;, el test se ve así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;rag_test&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"testing"&lt;/span&gt;

    &lt;span class="n"&gt;eval&lt;/span&gt; &lt;span class="s"&gt;"github.com/igcodinap/go-eval"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestRAGAnswerQuality&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EnvVar&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Skip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"eval skipped, set GOEVAL=1 to run"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;judge&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;newJudge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRunner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;judge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"Cuál es la capital de Francia?"&lt;/span&gt;
    &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;docs&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;myRAG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Case&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Input&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Metadata&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"flow"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;"rag.answer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"tier"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;"critical"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"dataset"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"smoke-v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Faithfulness&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Threshold&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hallucination&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Threshold&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AnswerRelevancy&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Threshold&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y se corre así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;GOEVAL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 go &lt;span class="nb"&gt;test&lt;/span&gt; ./...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La diferencia es conceptual. El test ya no solo pregunta "el código funcionó?". Ahora también pregunta:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;la respuesta está apoyada por el contexto?&lt;/li&gt;
&lt;li&gt;inventó información?&lt;/li&gt;
&lt;li&gt;respondió realmente la pregunta?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eso es lo que falta cuando probamos agentes solamente con asserts deterministas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Métricas incluidas
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;go-eval&lt;/code&gt; parte por los casos más comunes en aplicaciones con LLMs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Métrica&lt;/th&gt;
&lt;th&gt;Qué mide&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Faithfulness&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Si los claims del output están soportados por el contexto&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Hallucination&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Si el output inventa hechos fuera del contexto&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AnswerRelevancy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Si el output responde el input del usuario&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ContextPrecision&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Si los documentos recuperados son relevantes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GEval&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Una rúbrica custom definida por el usuario&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Compound&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Varias dimensiones de calidad en una sola llamada al judge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Contains&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Check determinístico de substring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Regex&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Check determinístico con expresión regular&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;JSONPath&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Check determinístico sobre una ruta JSON simple&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;FieldCount&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cantidad mínima de campos JSON no nulos&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Hay dos familias importantes:&lt;/p&gt;

&lt;p&gt;Las métricas LLM-as-judge, que usan un modelo para evaluar calidad semántica.&lt;/p&gt;

&lt;p&gt;Y las métricas determinísticas, que no necesitan modelo y sirven para contratos duros: formato, campos, tool calls, estructuras JSON, rutas, nombres de acciones.&lt;/p&gt;

&lt;p&gt;Esa combinación es clave para agentes. No todo debe juzgarlo un LLM. Si el agente debe devolver &lt;code&gt;{"tool":"search"}&lt;/code&gt;, probablemente quieres un check determinístico. Si debe explicar por qué eligió esa herramienta, quizás quieres una rúbrica.&lt;/p&gt;

&lt;p&gt;Un detalle práctico: cuando estas métricas se ejecutan vía &lt;code&gt;Runner&lt;/code&gt;, también heredan el mismo gate &lt;code&gt;GOEVAL&lt;/code&gt;. Si quieres que un contrato determinístico falle siempre en CI, puedes usarlo como un test tradicional o llamar la métrica directamente. Si quieres que forme parte de la suite de evaluación, déjalo bajo &lt;code&gt;Runner&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evaluando outputs estructurados
&lt;/h2&gt;

&lt;p&gt;Muchos agentes no solo responden texto. También deciden acciones.&lt;/p&gt;

&lt;p&gt;Por ejemplo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"search_docs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"go context cancellation"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.82&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahí podemos combinar checks deterministas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestAgentActionShape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRunner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;runAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Busca documentación sobre cancelación de contextos en Go"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Case&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Expected&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"search_docs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustJSONPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"action.name"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FieldCount&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;MinFields&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto no reemplaza una evaluación semántica. La complementa.&lt;/p&gt;

&lt;p&gt;Primero verificamos que el agente habla el protocolo correcto. Después evaluamos si la decisión fue buena.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rúbricas custom con GEval
&lt;/h2&gt;

&lt;p&gt;Hay casos donde las métricas predefinidas no bastan.&lt;/p&gt;

&lt;p&gt;Por ejemplo, podemos querer evaluar si una respuesta es útil para un usuario junior, si evita jerga innecesaria, o si sigue una política interna.&lt;/p&gt;

&lt;p&gt;Para eso existe &lt;code&gt;GEval&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GEval&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Criteria&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;`
La respuesta debe ser técnicamente correcta, breve y accionable.
Debe explicar el problema sin asumir conocimiento avanzado de Go.
Debe incluir un siguiente paso concreto.
`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Steps&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"Verifica si la respuesta contesta directamente la pregunta."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"Evalúa si la explicación técnica es correcta."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"Penaliza respuestas vagas o demasiado largas."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;Threshold&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto es poderoso porque permite convertir criterio humano en una prueba que vive junto al código.&lt;/p&gt;

&lt;p&gt;No es perfecto. Sigue dependiendo de un juez. Pero es mucho mejor que no tener ninguna medición y confiar solamente en intuición.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compound: varias dimensiones en una sola llamada
&lt;/h2&gt;

&lt;p&gt;Llamar a un LLM tiene costo y latencia. Si queremos evaluar cinco dimensiones de una misma respuesta, no siempre queremos hacer cinco llamadas separadas.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Compound&lt;/code&gt; permite evaluar varias dimensiones en una sola llamada:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Compound&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Dimensions&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dimension&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="s"&gt;"correctness"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Rubric&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;"La respuesta debe ser factualmente correcta."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Threshold&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="s"&gt;"clarity"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Rubric&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;"La respuesta debe ser clara para una persona técnica."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Threshold&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="s"&gt;"actionability"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Rubric&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;"La respuesta debe incluir un próximo paso concreto."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;Threshold&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El resultado incluye un score promedio y también resultados por dimensión.&lt;/p&gt;

&lt;p&gt;Esto es especialmente útil cuando el agente no tiene un solo eje de calidad. En software real, rara vez basta con "correcto". También importa si es claro, seguro, útil, consistente y barato.&lt;/p&gt;

&lt;h2&gt;
  
  
  Precheck: no gastes tokens si ya falló lo obvio
&lt;/h2&gt;

&lt;p&gt;Una idea muy Go: primero los checks baratos.&lt;/p&gt;

&lt;p&gt;Si una respuesta ni siquiera tiene el formato esperado, no tiene sentido gastar tokens evaluando su calidad semántica.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Precheck&lt;/code&gt; compone dos métricas. Si la primera falla, la segunda no corre.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Precheck&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Pre&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="n"&gt;eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MustJSONPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"action.name"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;Main&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GEval&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Criteria&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"La acción elegida debe resolver la tarea del usuario."&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto permite construir suites más eficientes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;valida estructura&lt;/li&gt;
&lt;li&gt;valida decisión&lt;/li&gt;
&lt;li&gt;valida respuesta final&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Resultados como JSONL
&lt;/h2&gt;

&lt;p&gt;Los tests sirven para decir pass/fail. Pero en evaluación de LLMs también queremos observar tendencia:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bajó el score después de cambiar el prompt?&lt;/li&gt;
&lt;li&gt;subió la latencia?&lt;/li&gt;
&lt;li&gt;aumentaron los tokens?&lt;/li&gt;
&lt;li&gt;qué casos están fallando?&lt;/li&gt;
&lt;li&gt;qué dimensión del &lt;code&gt;Compound&lt;/code&gt; bajó?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;go-eval&lt;/code&gt; puede escribir un JSON por cada evaluación:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRunner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;judge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithResultSink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultResultSink&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y luego:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;GOEVAL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nv"&gt;GOEVAL_RESULTS_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.goeval/results go &lt;span class="nb"&gt;test&lt;/span&gt; ./...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eso genera:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.goeval/results/results.jsonl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cada línea incluye campos como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-04-27T12:00:00Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"test_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TestRAGAnswerQuality"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"metric"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Faithfulness"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.91&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"passed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"All factual claims are supported by the provided context."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;312&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"latency_ns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1200000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"metadata"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"flow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rag.answer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"critical"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dataset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"smoke-v1"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto abre la puerta a comparaciones entre runs, reportes y regresiones. El objetivo no es reemplazar &lt;code&gt;go test&lt;/code&gt;, sino enriquecerlo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tracing cuando el judge se porta raro
&lt;/h2&gt;

&lt;p&gt;Cuando trabajamos con LLMs, a veces el problema está en el prompt de evaluación, no en el código evaluado.&lt;/p&gt;

&lt;p&gt;Para debuggear eso existe &lt;code&gt;GOEVAL_TRACE&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;GOEVAL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nv"&gt;GOEVAL_TRACE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 go &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nt"&gt;-run&lt;/span&gt; TestRAGAnswerQuality
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto loguea el prompt enviado al judge y la respuesta recibida usando &lt;code&gt;t.Log&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Es muy útil localmente. No debería activarse en CI compartido si los prompts contienen datos sensibles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benchmarks para prompts y modelos
&lt;/h2&gt;

&lt;p&gt;Una parte bonita de hacerlo dentro del toolchain de Go es que también podemos usar benchmarks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;BenchmarkRAGFaithfulness&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRunner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newJudge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Case&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Input&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;"Cuál es la capital de Francia?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;"Paris es la capital de Francia."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"Paris es la capital de Francia."&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bench&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Faithfulness&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Threshold&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y correr:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;GOEVAL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 go &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-bench&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; old.txt
&lt;span class="c"&gt;# cambiar prompt, modelo o pipeline&lt;/span&gt;
&lt;span class="nv"&gt;GOEVAL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 go &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-bench&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-count&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; new.txt
benchstat old.txt new.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;eval.Bench&lt;/code&gt; reporta &lt;code&gt;ns/op&lt;/code&gt;, &lt;code&gt;tokens/op&lt;/code&gt;, &lt;code&gt;score_mean&lt;/code&gt; y &lt;code&gt;score_stddev&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Esto importa porque una mejora de calidad que duplica costo y latencia no siempre es una mejora.&lt;/p&gt;

&lt;h2&gt;
  
  
  El adapter de OpenAI vive fuera del core
&lt;/h2&gt;

&lt;p&gt;El core de &lt;code&gt;go-eval&lt;/code&gt; no tiene dependencias externas.&lt;/p&gt;

&lt;p&gt;El adapter de OpenAI vive en un módulo separado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"testing"&lt;/span&gt;

    &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="s"&gt;"github.com/sashabaranov/go-openai"&lt;/span&gt;

    &lt;span class="n"&gt;eval&lt;/span&gt; &lt;span class="s"&gt;"github.com/igcodinap/go-eval"&lt;/span&gt;
    &lt;span class="n"&gt;openaieval&lt;/span&gt; &lt;span class="s"&gt;"github.com/igcodinap/go-eval/adapters/openai"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;newJudge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Judge&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Helper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EnvVar&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Skip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"eval skipped, set GOEVAL=1 to run"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"OPENAI_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Skip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"OPENAI_API_KEY not set"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;openaieval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewJudge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GPT4oMini&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La interfaz es lo suficientemente pequeña para escribir adapters propios: OpenAI, Ollama, Anthropic, Gemini, un modelo interno, o incluso un judge determinístico para tests locales.&lt;/p&gt;

&lt;p&gt;Lo importante es que el resto de tu suite no depende del proveedor.&lt;/p&gt;

&lt;p&gt;Depende de &lt;code&gt;eval.Judge&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por qué esto importa para Go
&lt;/h2&gt;

&lt;p&gt;Mi apuesta es simple: Go puede ser uno de los mejores lenguajes para construir aplicaciones agénticas.&lt;/p&gt;

&lt;p&gt;No porque tenga más hype.&lt;/p&gt;

&lt;p&gt;Sino porque las aplicaciones agénticas reales son sistemas distribuidos pequeños:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reciben eventos&lt;/li&gt;
&lt;li&gt;llaman APIs&lt;/li&gt;
&lt;li&gt;hacen I/O&lt;/li&gt;
&lt;li&gt;usan contexto y cancelación&lt;/li&gt;
&lt;li&gt;ejecutan herramientas&lt;/li&gt;
&lt;li&gt;mantienen estado&lt;/li&gt;
&lt;li&gt;necesitan observabilidad&lt;/li&gt;
&lt;li&gt;deben correr barato&lt;/li&gt;
&lt;li&gt;deben fallar de forma entendible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ese mundo se parece mucho más a Go que a un notebook.&lt;/p&gt;

&lt;p&gt;Pero para que Go salte como lenguaje por defecto en este espacio, no basta con buenos SDKs para llamar modelos. Necesitamos buenas formas de probar comportamiento.&lt;/p&gt;

&lt;p&gt;Necesitamos que escribir un test de calidad para un agente sea tan natural como escribir un test de una API HTTP.&lt;/p&gt;

&lt;p&gt;Ese es el lugar que quiero que ocupe &lt;code&gt;go-eval&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Estado actual
&lt;/h2&gt;

&lt;p&gt;Hoy &lt;code&gt;go-eval&lt;/code&gt; está en una etapa temprana, pero útil.&lt;/p&gt;

&lt;p&gt;La versión &lt;code&gt;v0.3&lt;/code&gt; incluye:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;métricas RAG básicas&lt;/li&gt;
&lt;li&gt;rúbricas custom con &lt;code&gt;GEval&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;evaluación multi-dimensión con &lt;code&gt;Compound&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;checks determinísticos para texto y JSON&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Precheck&lt;/code&gt; para ahorrar llamadas al judge&lt;/li&gt;
&lt;li&gt;adapters OpenAI y Ollama&lt;/li&gt;
&lt;li&gt;resultados JSONL&lt;/li&gt;
&lt;li&gt;comparación de resultados JSONL&lt;/li&gt;
&lt;li&gt;loaders JSON para datasets y golden cases&lt;/li&gt;
&lt;li&gt;tracing de prompts y respuestas&lt;/li&gt;
&lt;li&gt;benchmarks integrados al flujo de Go&lt;/li&gt;
&lt;li&gt;una CLI mínima con &lt;code&gt;goeval test&lt;/code&gt;, &lt;code&gt;goeval compare&lt;/code&gt; y &lt;code&gt;goeval version&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Todavía no pretende ser una plataforma completa.&lt;/p&gt;

&lt;p&gt;No hay dashboard hosted.&lt;/p&gt;

&lt;p&gt;No hay evaluación multi-turn first-class todavía.&lt;/p&gt;

&lt;p&gt;No hay loaders YAML oficiales en el core.&lt;/p&gt;

&lt;p&gt;Y eso está bien. La idea no es clonar todo el ecosistema Python de una vez. La idea es construir una base idiomática, pequeña y extensible.&lt;/p&gt;

&lt;p&gt;Para las próximas versiones, el roadmap apunta a:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;evaluación de conversaciones&lt;/li&gt;
&lt;li&gt;reportes más ricos sobre resultados JSONL&lt;/li&gt;
&lt;li&gt;más adapters&lt;/li&gt;
&lt;li&gt;mejores workflows para comparar calidad, costo y latencia entre modelos&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Cómo empezar
&lt;/h2&gt;

&lt;p&gt;Instalación:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go get github.com/igcodinap/go-eval
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Crear un test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;TestAgentAnswer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;judge&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;newJudge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRunner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;judge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Explica context.Context en Go"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Case&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Input&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;"Explica context.Context en Go"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Output&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eval&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GEval&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Criteria&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;"La respuesta debe ser correcta, breve y útil para un gopher intermedio."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Threshold&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ejecutar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;GOEVAL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 go &lt;span class="nb"&gt;test&lt;/span&gt; ./...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Guardar resultados:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;GOEVAL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nv"&gt;GOEVAL_RESULTS_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.goeval/results go &lt;span class="nb"&gt;test&lt;/span&gt; ./...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Debuggear prompts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;GOEVAL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nv"&gt;GOEVAL_TRACE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 go &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; ./...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Cierre
&lt;/h2&gt;

&lt;p&gt;Los tests tradicionales nos dicen si el software hizo lo que especificamos.&lt;/p&gt;

&lt;p&gt;Las evaluaciones nos ayudan a descubrir si el sistema se comportó como esperábamos.&lt;/p&gt;

&lt;p&gt;En aplicaciones con agentes, necesitamos ambas cosas.&lt;/p&gt;

&lt;p&gt;Mi objetivo con &lt;code&gt;go-eval&lt;/code&gt; es que la comunidad Go tenga una librería pequeña, idiomática y poderosa para medir calidad de sistemas con LLMs sin abandonar el flujo natural del lenguaje.&lt;/p&gt;

&lt;p&gt;Quiero que cuando alguien construya un agente en Go, el siguiente paso obvio no sea abrir una planilla ni escribir un script en otro lenguaje.&lt;/p&gt;

&lt;p&gt;Quiero que sea crear un &lt;code&gt;_test.go&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;GOEVAL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 go &lt;span class="nb"&gt;test&lt;/span&gt; ./...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si logramos eso, Go no solo va a ser una buena opción para servir aplicaciones agénticas.&lt;/p&gt;

&lt;p&gt;Va a ser una de las mejores opciones para construirlas con confianza.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recursos
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/igcodinap/go-eval" rel="noopener noreferrer"&gt;https://github.com/igcodinap/go-eval&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Ejemplo RAG: &lt;a href="https://github.com/igcodinap/go-eval/tree/main/examples/rag_app" rel="noopener noreferrer"&gt;https://github.com/igcodinap/go-eval/tree/main/examples/rag_app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Adapter OpenAI: &lt;a href="https://github.com/igcodinap/go-eval/tree/main/adapters/openai" rel="noopener noreferrer"&gt;https://github.com/igcodinap/go-eval/tree/main/adapters/openai&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>go</category>
      <category>ai</category>
      <category>testing</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Migrando de Supabase a una arquitectura full Go: La poesía de los binarios estáticos</title>
      <dc:creator>igcodinap</dc:creator>
      <pubDate>Tue, 13 Jan 2026 14:26:48 +0000</pubDate>
      <link>https://dev.to/gopherscl/migrando-de-supabase-a-una-arquitectura-full-go-la-poesia-de-los-binarios-estaticos-145n</link>
      <guid>https://dev.to/gopherscl/migrando-de-supabase-a-una-arquitectura-full-go-la-poesia-de-los-binarios-estaticos-145n</guid>
      <description>&lt;p&gt;&lt;em&gt;Cómo el ecosistema Go nos permitió reemplazar un backend hosted por una infraestructura self-hosted elegante, mantenible y completamente gratuita.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Hace unos meses tomamos una decisión que parecía arriesgada: migrar una aplicación desde Supabase hosted hacia una arquitectura completamente self-hosted. El resultado fue inesperadamente hermoso.&lt;/p&gt;

&lt;h2&gt;
  
  
  El contexto
&lt;/h2&gt;

&lt;p&gt;Teníamos una instancia Supabase para autenticación y base de datos. Funcionaba bien, pero dependíamos de sus restricciones y limitaciones  para seguir creando features. Cuando el proyecto escaló, se hizo cada vez más difícil mantenerlo y añadir nuevos features debido al mismo diseño de supabase. Por esto, decidimos tomar control total de nuestra infraestructura.&lt;/p&gt;

&lt;p&gt;Lo que no esperábamos era descubrir que todo el ecosistema que necesitábamos estaba escrito en Go.&lt;/p&gt;

&lt;h2&gt;
  
  
  La arquitectura resultante
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    ┌─────────────────────────────────────────┐
                    │         GCP Compute Engine VM           │
                    │                                         │
    Internet ───────┤  Caddy ──▶ go-app ──▶ Cloud SQL Proxy  │──▶ PostgreSQL
                    │    │                        │           │
                    │    └─────▶ GoTrue ──────────┘           │
                    │                                         │
                    │         Alloy ──▶ Grafana Cloud         │
                    └─────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cinco servicios. &lt;strong&gt;Todos escritos en Go.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Servicio&lt;/th&gt;
&lt;th&gt;Propósito&lt;/th&gt;
&lt;th&gt;Creador&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;go-app&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Nuestra API REST&lt;/td&gt;
&lt;td&gt;Nosotros&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Caddy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Reverse proxy + TLS automático&lt;/td&gt;
&lt;td&gt;Matt Holt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GoTrue&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Autenticación (signup, login, OAuth)&lt;/td&gt;
&lt;td&gt;Supabase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Alloy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Métricas y logs&lt;/td&gt;
&lt;td&gt;Grafana Labs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cloud SQL Proxy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Conexión segura a Cloud SQL&lt;/td&gt;
&lt;td&gt;Google&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  ¿Por qué esto es especial?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Binarios estáticos
&lt;/h3&gt;

&lt;p&gt;Cada uno de estos servicios compila a un único binario sin dependencias externas. No hay runtime que instalar, no hay versiones de Node que gestionar, no hay virtualenvs. Un binario, un proceso.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Así de simple es desplegar Caddy&lt;/span&gt;
wget https://github.com/caddyserver/caddy/releases/download/v2.8.4/caddy_2.8.4_linux_amd64.tar.gz
&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xzf&lt;/span&gt; caddy_2.8.4_linux_amd64.tar.gz
./caddy run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Consumo de recursos mínimo
&lt;/h3&gt;

&lt;p&gt;Nuestra VM de staging corre los 5 servicios con &lt;strong&gt;2GB de RAM&lt;/strong&gt;. En idle, el consumo total no supera los 400MB. Comparen eso con correr un cluster de Node.js + Redis + Nginx.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Configuración declarativa
&lt;/h3&gt;

&lt;p&gt;Caddy, GoTrue y Alloy usan archivos de configuración simples y legibles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Caddyfile - 40 líneas para TLS + reverse proxy + CORS
api.dominio.com {
  handle /auth/v1/* {
    reverse_proxy gotrue:9999
  }
  handle {
    reverse_proxy api:8080
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. El mismo idioma mental
&lt;/h3&gt;

&lt;p&gt;Cuando debuggeas un problema, saltas entre servicios pero te mantienes en el mismo paradigma. Context propagation, structured logging, graceful shutdown - todos siguen los mismos patrones porque todos son Go.&lt;/p&gt;

&lt;h2&gt;
  
  
  GoTrue: El corazón de la autenticación
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/supabase/auth" rel="noopener noreferrer"&gt;GoTrue&lt;/a&gt; es el servicio de autenticación de Supabase, pero es completamente standalone. Lo puedes correr con Docker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# docker-compose.yml&lt;/span&gt;
&lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;supabase/auth:v2.184.0&lt;/span&gt;
  &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;GOTRUE_DB_DATABASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres://user:pass@db:5432/mydb?search_path=auth&lt;/span&gt;
    &lt;span class="na"&gt;GOTRUE_JWT_SECRET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${JWT_SECRET}&lt;/span&gt;
    &lt;span class="na"&gt;GOTRUE_SITE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://miapp.com&lt;/span&gt;
  &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sh"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-c"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gotrue&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;migrate&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;gotrue&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;serve"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y obtienes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Signup/login con email y password&lt;/li&gt;
&lt;li&gt;Magic links&lt;/li&gt;
&lt;li&gt;OAuth (Google, Apple, GitHub, etc.)&lt;/li&gt;
&lt;li&gt;Refresh tokens&lt;/li&gt;
&lt;li&gt;JWT estándar compatible con cualquier middleware&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lo mejor: &lt;strong&gt;es el mismo servicio que usa Supabase en producción&lt;/strong&gt;. No es una versión recortada.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caddy: TLS sin dolor
&lt;/h2&gt;

&lt;p&gt;Si nunca han usado Caddy, prepárense para olvidar todo lo que saben sobre configurar certificados SSL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;api.miapp.com {
  reverse_proxy localhost:8080
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eso es todo. Caddy obtiene certificados de Let's Encrypt automáticamente, los renueva, y configura HTTPS con los headers de seguridad correctos. Cero configuración adicional.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alloy: Observabilidad moderna
&lt;/h2&gt;

&lt;p&gt;Grafana Alloy (antes Grafana Agent) recolecta métricas y logs y los envía a Grafana Cloud (que tiene un tier gratuito generoso).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Recolectar logs de todos los containers Docker
loki.source.docker "containers" {
  host = "unix:///var/run/docker.sock"
  targets = discovery.docker.containers.targets
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Con 20 líneas de configuración tienes logs centralizados de todos tus servicios, con labels automáticos por container.&lt;/p&gt;

&lt;h2&gt;
  
  
  El flujo de autenticación
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Usuario                    Frontend                   Caddy                    GoTrue                     GoApp
   │                          │                         │                         │                         │
   │──── Login ──────────────▶│                         │                         │                         │
   │                          │─── POST /auth/v1/token ▶│                         │                         │
   │                          │                         │──── proxy ─────────────▶│                         │
   │                          │                         │                         │── validate credentials ─│
   │                          │                         │◀─── JWT ────────────────│                         │
   │                          │◀── { access_token } ────│                         │                         │
   │◀─── Logged in ───────────│                         │                         │                         │
   │                          │                         │                         │                         │
   │──── Get entity ─────────▶│                         │                         │                         │
   │                          │── GET /api/entity ─────▶│                         │                         │
   │                          │   Authorization: Bearer │──── proxy ──────────────────────────────────────▶│
   │                          │                         │                         │                         │── validate JWT
   │                          │                         │                         │                         │── query DB
   │                          │◀──────────────────────────────────────────────────── { entity } ───────────│
   │◀─── entity ──────────────│                         │                         │                         │
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;entity: cualquiera sea el recurso de tu db que quieras consumir.&lt;/p&gt;

&lt;p&gt;El JWT que emite GoTrue es validado por nuestro middleware en go-app. Mismo secreto, mismo formato, cero fricción.&lt;/p&gt;

&lt;h2&gt;
  
  
  Costos
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Recurso&lt;/th&gt;
&lt;th&gt;Costo mensual&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;VM e2-small (staging)&lt;/td&gt;
&lt;td&gt;~$15 USD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloud SQL (db-f1-micro)&lt;/td&gt;
&lt;td&gt;~$10 USD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grafana Cloud&lt;/td&gt;
&lt;td&gt;$0 (tier gratuito)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Caddy, GoTrue, Alloy&lt;/td&gt;
&lt;td&gt;$0 (open source)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total staging&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$25 USD&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Para producción duplicamos los recursos, pero seguimos muy por debajo de lo que pagábamos por Supabase Pro.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lo que aprendimos
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Self-hosted no significa complejo
&lt;/h3&gt;

&lt;p&gt;Con las herramientas correctas, self-hosted puede ser más simple que managed. Un &lt;code&gt;docker-compose up -d&lt;/code&gt; y tienes todo corriendo.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Go es infraestructura
&lt;/h3&gt;

&lt;p&gt;El ecosistema Go para herramientas de infraestructura es impresionante: Docker, Kubernetes, Terraform, Prometheus, Grafana, Caddy, Traefik, Hugo... La lista es interminable.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Supabase hizo bien su trabajo
&lt;/h3&gt;

&lt;p&gt;GoTrue es un proyecto de calidad. Está bien documentado, es estable, y hace una cosa bien. Que sea open source y self-hosteable habla bien de Supabase como empresa.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. La cohesión tiene valor
&lt;/h3&gt;

&lt;p&gt;Hay algo satisfactorio en ver &lt;code&gt;docker ps&lt;/code&gt; y saber que todos esos procesos comparten el mismo ADN. Cuando algo falla, el debugging sigue los mismos patrones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recursos
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/supabase/auth" rel="noopener noreferrer"&gt;GoTrue GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://caddyserver.com/" rel="noopener noreferrer"&gt;Caddy Server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://grafana.com/oss/alloy/" rel="noopener noreferrer"&gt;Grafana Alloy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://huma.rocks/" rel="noopener noreferrer"&gt;Huma v2&lt;/a&gt; - Framework HTTP para Go que usamos en la go-app&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusión
&lt;/h2&gt;

&lt;p&gt;Si están considerando migrar de un servicio hosted a self-hosted, el ecosistema Go les tiene cubiertos. No necesitan reinventar la rueda - solo necesitan ensamblar las piezas correctas.&lt;/p&gt;

&lt;p&gt;Y si ya programan en Go, van a sentirse como en casa. &lt;/p&gt;




&lt;p&gt;&lt;em&gt;¿Preguntas sobre la migración o la arquitectura? Déjenlas en los comentarios o encuéntrenme en la comunidad de Golang Chile.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>infrastructure</category>
      <category>supabase</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
