{
    "version": "https://jsonfeed.org/version/1",
    "title": "WiLine Edge Cloud — AI Tutorials",
    "home_page_url": "https://wec.wiline.com/docs/es/tutorials/",
    "description": "Tested, reproducible tutorials for self-hosting AI infrastructure on WiLine.",
    "items": [
        {
            "id": "https://wec.wiline.com/docs/es/tutorials/deploy-llm-gateway-wec-instance/",
            "content_html": "<div class=\"tutorialHero\"><svg viewBox=\"0 0 24 24\" fill=\"#2496ED\" aria-label=\"Docker\" class=\"tutorialHero__docker\"><path d=\"M13.983 11.078h2.119a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.119a.185.185 0 00-.185.185v1.888c0 .102.083.185.185.185m-2.954-5.43h2.118a.186.186 0 00.186-.186V3.574a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m0 2.716h2.118a.187.187 0 00.186-.186V6.29a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.887c0 .102.082.185.185.186m-2.93 0h2.12a.186.186 0 00.184-.186V6.29a.185.185 0 00-.185-.185H8.1a.185.185 0 00-.185.185v1.887c0 .102.083.185.185.186m-2.964 0h2.119a.186.186 0 00.185-.186V6.29a.185.185 0 00-.185-.185H5.136a.186.186 0 00-.186.185v1.887c0 .102.084.185.186.186m5.893 2.715h2.118a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m-2.93 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.083.185.185.185m-2.964 0h2.119a.185.185 0 00.185-.185V9.006a.185.185 0 00-.184-.186h-2.12a.186.186 0 00-.186.186v1.887c0 .102.084.185.186.185m-2.92 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.082.185.185.185M23.763 9.89c-.065-.051-.672-.51-1.954-.51-.338.001-.676.03-1.01.087-.248-1.7-1.653-2.53-1.716-2.566l-.344-.199-.226.327c-.284.438-.49.922-.612 1.43-.23.97-.09 1.882.403 2.661-.595.332-1.55.413-1.744.42H.751a.751.751 0 00-.75.748 11.376 11.376 0 00.692 4.062c.545 1.428 1.355 2.48 2.41 3.124 1.18.723 3.1 1.137 5.275 1.137.983.003 1.963-.086 2.93-.266a12.248 12.248 0 003.823-1.389c.98-.567 1.86-1.288 2.61-2.136 1.252-1.418 1.998-2.997 2.553-4.4h.221c1.372 0 2.215-.549 2.68-1.009.309-.293.55-.65.707-1.046l.098-.288Z\"></path></svg><span class=\"tutorialHero__plus\">+</span><img class=\"tutorialHero__brand\" src=\"https://wec.wiline.com/docs/es/assets/images/litellm-wordmark-86d6f0432720b27534e05bc579dfc29c.png\" alt=\"LiteLLM\"><span class=\"tutorialHero__plus\">+</span><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"242\" height=\"255\" fill=\"none\" viewBox=\"0 0 242 255\" class=\"tutorialHero__wiline\"><path fill=\"#4E8EF7\" d=\"M56.711 11.293A195 195 0 0 1 41.899 4.79c4.788 1.85 9.71 4.028 14.812 6.504M73.807 29.35c-1.302-1.446-2.666-3.233-4.02-5.38 1.326 1.44 2.642 3.24 4.02 5.38M69.761 23.354c-.832-.585-1.752-1.472-2.558-2.714.9.567 1.685 1.49 2.558 2.714M62.97 15.774a10.7 10.7 0 0 1-3.158-1.841c.993.299 1.986.956 3.159 1.841M59.715 13.346c-.818-.194-1.782-.674-2.829-1.494.84.175 1.761.691 2.83 1.494M39.317 3.782c-.524.192-1.413.302-2.455.14.594-.255 1.342-.238 2.455-.14M74.906 31.339c-.334-.153-.73-.61-1.117-1.398.358.143.706.619 1.117 1.398M66.024 18.878c-.239.1-.557.05-.752-.276.35-.201.532-.069.752.276M71.773 150.625c3.363 7.291 7.185 14.411 9.983 21.914 4.474 11.997 11.167 20.832 24.296 24.757 10.62 3.175 20.369 1.795 30.093-1.816 9.937-3.689 15.165-11.978 18.501-21.541 4.349-12.466 9.385-24.443 19.668-33.446 11.32-9.909 24.639-14.788 39.429-15.63 8.3-.473 16.649-.081 25.444.444.472 15.673.385 30.825.55 45.974.038 3.499-1.049 4.719-4.557 4.543-4.982-.25-9.987-.079-14.98.009-.209.004-.4.991-.6 1.521-23.106 1.695-37.691 15.22-47.961 34.651-3.387 6.408-5.913 13.38-10.013 19.273-4.368 6.277-10.134 11.582-15.853 17.196-6.439 1.421-12.244 3.342-18.202 4.416-12.627 2.274-24.661.457-35.764-6.356-1.028-.631-2.395-.709-3.604-1.043 0 0-.117-.11-.047-.495-.818-.973-1.706-1.562-2.593-2.15 0 0 .09-.044.05-.335-.513-.545-.988-.798-1.463-1.052 0 0-.07-.123.012-.492-.561-.894-1.205-1.421-1.848-1.947 0 0 .134-.068.102-.28-.321-.442-.61-.671-.898-.901 0 0 .086-.007.074-.336-2.05-3.429-4.089-6.529-6.127-9.629 0 0 .094-.017.123-.365-3.013-6.117-6.056-11.886-9.1-17.655 0 0 .096-.01.088-.342-1.978-4.073-3.89-7.848-5.997-11.51-.246-.428-1.572-.235-2.398-.329 0 0-.226-.202-.114-.613-.213-.499-.537-.587-.861-.674 0 0-.032-.101.02-.455-.62-.846-1.291-1.339-1.964-1.832 0 0 .133-.083.119-.372-2.476-4.624-5.645-8.048-10.914-8.777 0 0 .108-.031.01-.326-1.673-1.444-3.105-2.918-4.85-3.688-9.751-4.308-20.013-5.643-30.63-5.23-7.305.285-7.316.051-7.317-7.446q-.002-18.496-.002-36.992v-3.57h36.106s-.08.019.133.283c1.43.415 2.646.567 3.863.719 0 0-.077.022.113.296 1.804.591 3.417.908 5.03 1.225 0 0 .139-.037.152.369 1.592.962 3.17 1.517 4.748 2.073 0 0-.091.027.052.267.735.415 1.328.59 1.92.765 0 0-.076.033.053.246.477.325.825.438 1.173.551 0 0-.149.018-.07.246.418.239.757.251 1.096.263 0 0 .23.058.189.465 1.522 1.319 3.084 2.231 4.647 3.143 0 0-.105.033-.015.316 2.723 2.764 5.356 5.244 7.989 7.724 0 0-.104.062-.094.352 1.01 1.401 2.01 2.512 3.01 3.624\"></path><path fill=\"#4E8EF7\" d=\"M214.77 52.819c-3.343.846-6.723 1.574-10.024 2.562-11.388 3.408-20.567 10.142-27.166 19.865-5.097 7.511-9.621 15.483-13.685 23.608-6.809 13.613-15.725 24.809-31.343 28.198-24.707 5.362-45.22-.126-57.852-24.564C69.466 92.363 63.8 82.375 57.349 73c-3.369-4.896-8.866-8.326-13.679-13.041-1.11-1.409-1.81-2.472-2.794-2.937-10.697-5.056-21.87-7.3-33.686-5.344-3.877.641-5.72-.297-5.615-4.86.294-12.816.26-25.646.019-38.464C1.509 3.874 3.15 2.5 7.429 2.716c9.774.492 19.564.678 29.347.989 0 0-.076.026.082.257 1.046.122 1.935.012 2.825-.098.575.043 1.15.086 2.038.692 5.265 2.72 10.218 4.877 15.17 7.034 0 0-.108.03-.016.317 1.058.765 2.022 1.245 2.987 1.724 0 0-.113.048-.082.354 1.144.876 2.258 1.446 3.372 2.017 0 0 .18.145.097.506.223.404.53.447.837.49 0 0 .214.13.144.504.244.426.558.477.873.528 0 0 .171.124.108.485.256.412.574.464.893.515 0 0 .167.127.105.493.265.437.592.509.919.58 0 0 .078.108.019.5.861 1.28 1.782 2.166 2.702 3.053 0 0-.101.061-.087.368 1.379 2.093 2.743 3.88 4.107 5.666 0 0-.094.011-.092.297.4.742.795 1.2 1.19 1.656 0 0-.121.02-.165.337.685 1.432 1.413 2.549 2.14 3.665 0 0-.11.024-.129.299.326.776.67 1.277 1.014 1.78 0 0-.084-.005-.1.367.933 4.079.311 8.497 4.648 10.67 4.69 12.164 12.107 21.794 25.852 23.497 19.618 2.431 35.321.062 45.299-24.554 3.398-8.383 7.099-16.902 12.232-24.264 8.353-11.979 21.046-17.795 35.832-20.32 1.666.417 2.649.651 3.624.622 9.93-.298 19.865-.513 29.785-1.032 3.818-.2 4.825 1.198 4.746 4.853-.218 9.967.004 19.944-.109 29.915-.052 4.583 1.149 10.606-1.239 13.302-2.044 2.308-8.394.86-12.842.967-3.82.092-7.644.036-11.467.045.065.31.097.633.213.922.034.084.306.072.469.105M37.585 127.354c-11.585.344-23.368.344-35.907.344v3.57q0 18.496.002 36.992c0 7.497.012 7.731 7.318 7.446 10.616-.413 20.878.922 30.63 5.23 1.743.77 3.176 2.244 4.84 3.744-13.655-7.873-28.93-7.767-44.468-7.398v-52.503c12.516.308 25.063-1.222 37.585 2.575\"></path><path fill=\"#4E8EF7\" d=\"M36.619 3.401c-9.626-.007-19.415-.193-29.19-.685C3.151 2.5 1.51 3.874 1.594 8.354c.241 12.818.275 25.648-.018 38.464-.105 4.563 1.737 5.501 5.614 4.86 11.816-1.956 22.99.288 33.686 5.344.984.465 1.685 1.529 2.616 2.66-13.39-7.538-28.284-7.343-43.479-6.78V0C12.314 1.045 24.388 2.071 36.62 3.401M215.232 52.852c-.625-.066-.897-.054-.931-.138-.116-.29-.148-.612-.212-.922 3.822-.01 7.646.047 11.467-.045 4.448-.108 10.797 1.341 12.842-.967 2.387-2.696 1.186-8.719 1.238-13.302.114-9.971-.109-19.948.109-29.915.08-3.655-.928-5.052-4.746-4.853-9.919.519-19.855.734-29.785 1.032-.975.03-1.958-.205-3.228-.58C214.559 1.945 227.423.991 240.73.003v52.882c-8.276 0-16.656 0-25.498-.033M220.071 177.398c-.271-.573-.08-1.561.129-1.565 4.993-.088 9.998-.259 14.98-.009 3.508.176 4.595-1.044 4.557-4.543-.165-15.149-.078-30.301-.088-45.911.458.372 1.32 1.202 1.324 2.036.079 16.424.059 32.848.059 50.036-6.984 0-13.737 0-20.961-.044M88.39 245.741c1.022.084 2.39.162 3.417.793 11.102 6.813 23.137 8.63 35.763 6.356 5.959-1.073 11.764-2.995 17.871-4.34-5.612 2.132-11.343 5.166-17.325 5.79-13.898 1.451-27.803 1.213-39.725-8.599M66.556 210.212c2.975 5.411 6.018 11.18 9.02 17.344-3.011-5.4-5.982-11.193-9.02-17.344M58.341 197.934c.666-.167 1.992-.36 2.238.068 2.107 3.662 4.018 7.437 5.985 11.557-2.69-3.535-5.376-7.449-8.223-11.625M44.637 185.241c5.099.438 8.268 3.862 10.737 8.564-3.518-2.513-7.043-5.393-10.737-8.564M82.29 48.422c-4.252-1.834-3.63-6.252-4.548-10.37 1.508 3.069 2.986 6.55 4.548 10.37M68.757 146.326c-2.533-2.157-5.166-4.637-7.882-7.455 2.539 2.151 5.16 4.641 7.882 7.455M75.532 228.226a122 122 0 0 1 6.047 9.319c-1.993-2.742-3.986-5.854-6.047-9.319M71.695 150.312c-.922-.799-1.921-1.91-2.906-3.366.952.788 1.89 1.921 2.906 3.366M46.663 130.007c-1.353-.103-2.966-.42-4.767-1.051 1.377.07 2.941.454 4.767 1.051M51.647 132.369a13.1 13.1 0 0 1-4.501-1.77c1.48.227 2.902.852 4.5 1.77M60.724 138.321a17.7 17.7 0 0 1-4.395-2.781c1.501.609 2.869 1.551 4.395 2.781M76.889 35.31c-.674-.781-1.402-1.898-2.068-3.39.711.768 1.362 1.912 2.068 3.39M85.668 243.124c.782.31 1.67.899 2.423 1.825-.862-.29-1.59-.919-2.423-1.825M41.596 128.372c-1.033.176-2.25.024-3.68-.432 1.023-.169 2.26-.032 3.68.432M55.367 194.37c.568.222 1.24.715 1.808 1.548-.637-.199-1.17-.738-1.808-1.548M82.397 239.295c.56.251 1.204.778 1.71 1.628-.633-.235-1.13-.794-1.71-1.628M84.291 241.679c.334.034.809.287 1.298.875-.376.004-.766-.326-1.298-.875M53.64 133.426c-.438.094-1.03-.081-1.764-.543.441-.101 1.026.086 1.764.543M77.805 37.409c-.322-.188-.666-.69-.978-1.52.34.182.648.693.978 1.52M81.588 238.108c.218-.039.507.19.823.707-.233.045-.493-.196-.823-.707M55.906 134.832c-.198.157-.537.145-.92-.161.262-.29.537-.195.92.161M57.276 196.552c.254-.079.578.009.756.361-.377.171-.557.018-.756-.361M54.888 134.25c-.215.129-.563.017-1.038-.351.217-.133.56-.012 1.038.351M64.014 16.858a.545.545 0 0 1-.71-.247c.344-.19.51-.067.71.247M65.027 17.882a.574.574 0 0 1-.734-.272c.35-.193.525-.063.734.272M67.045 19.939c-.244.093-.57.021-.777-.329.358-.192.55-.044.777.329\"></path></svg></div>\n<div class=\"skillTracker\" tabindex=\"0\" aria-label=\"Skill path: 0 of 4 skills earned\"><div class=\"skillTracker__ring\" style=\"--skillTracker-pct:0%\"><span class=\"skillTracker__ringLabel\">0<!-- -->/<!-- -->4</span></div><div class=\"skillTracker__panel\"><div class=\"skillTracker__header\"><span class=\"skillTracker__title\">🎯 Skill path</span><span class=\"skillTracker__count\">0<!-- -->/<!-- -->4<!-- --> earned</span></div><div class=\"skillTracker__series\">Self-hosting an LLM gateway</div><ul class=\"skillTracker__steps\"><li><span class=\"skillTracker__step\"><span class=\"skillTracker__dot\" data-state=\"current\">1</span><span class=\"skillTracker__skill\" data-state=\"current\">One endpoint, scoped keys</span></span></li><li><span class=\"skillTracker__step\"><span class=\"skillTracker__dot\" data-state=\"locked\">2</span><span class=\"skillTracker__skill\" data-state=\"locked\">Route work to the right model</span></span></li><li><span class=\"skillTracker__step\"><span class=\"skillTracker__dot\" data-state=\"locked\">3</span><span class=\"skillTracker__skill\" data-state=\"locked\">Mask PII at the gateway</span></span></li><li><span class=\"skillTracker__step\"><span class=\"skillTracker__dot\" data-state=\"locked\">🏆</span><span class=\"skillTracker__skill\" data-state=\"locked\">Budgets and real cost</span></span></li></ul><div class=\"skillTracker__footer\">Finish this tutorial, then mark it complete below to earn the skill.</div></div></div>\n<p>Así es como suele ir. Una aplicación necesita un modelo, así que pegas la clave API en\nsu <code>.env</code>. Luego, una segunda aplicación necesita una. Luego un script. Seis meses después, la misma\nclave está en cinco lugares, nadie recuerda cuál de ellas sigue funcionando, y no\npuedes rotarla sin romper algo que solo descubrirás cuando se rompa.</p>\n<p>Un gateway es la solución aburrida. Un endpoint frente a cada modelo, un lugar que\nmantiene la verdadera credencial, y una clave específica por aplicación que puedes revocar por sí sola.\nEsta publicación despliega uno en una instancia WEC y lo apunta a la API de Inferencia WEC.</p>\n<!-- -->\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"lo-que-estamos-poniendo-en-la-caja\">Lo que estamos poniendo en la caja<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-llm-gateway-wec-instance/#lo-que-estamos-poniendo-en-la-caja\" class=\"hash-link\" aria-label=\"Enlace directo al Lo que estamos poniendo en la caja\" title=\"Enlace directo al Lo que estamos poniendo en la caja\" translate=\"no\">​</a></h2>\n<p>El host ya ejecuta cinco otras pilas, lo cual es importante: este es el caso normal,\nno una VM limpia. Antes de agregar nada, verifica qué está escuchando y qué está libre:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">ps</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--format</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'{{.Names}}\\t{{.Ports}}'</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">sudo</span><span class=\"token plain\"> ss </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-tlnp</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">grep</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-E</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">':(4000|5432|5433)\\b'</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">langfuse-postgres-1   127.0.0.1:5433-&gt;5432/tcp</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">langfuse-clickhouse-1 127.0.0.1:8123-&gt;8123/tcp, 127.0.0.1:9000-&gt;9000/tcp</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">openclaw-caddy-1      100.87.239.229:80-&gt;80/tcp, 100.87.239.229:443-&gt;443/tcp</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">...</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">LISTEN 0  244   127.0.0.1:5432   users:((\"postgres\",pid=892))</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">LISTEN 0  4096  127.0.0.1:5433   users:((\"docker-proxy\"))</span><br></div></code></pre></div></div>\n<p>El puerto 4000 está libre. Postgres 5432 pertenece al host y 5433 a Langfuse, así que la\nbase de datos del gateway no obtiene ninguno: no publicará un puerto en absoluto.</p>\n<p>Luego confirma que el backend responde antes de poner algo frente a él:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-s</span><span class=\"token plain\"> https://inference.wiline.com/v1/models </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Authorization: Bearer </span><span class=\"token string variable\" style=\"color:#36acaa\">$WEC_API_KEY</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> jq </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-r</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'.data[].id'</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">whisper-large-v3</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">gemma4</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">wiline-coding</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">zai-org/GLM-5.2</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Qwen2.5-3B-Instruct</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Qwen3.5:9B</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Qwen3.5-122B</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">kokoro</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Llama3.1-8B-Instruct</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">bge-m3</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Qwen3.5-9B</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">wiline-auto</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">wiline-cost</span><br></div></code></pre></div></div>\n<p>Trece modelos en una clave. Eso es lo que estamos a punto de dejar de repartir.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"la-pila\">La pila<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-llm-gateway-wec-instance/#la-pila\" class=\"hash-link\" aria-label=\"Enlace directo al La pila\" title=\"Enlace directo al La pila\" translate=\"no\">​</a></h2>\n<p>Dos contenedores: el gateway y un Postgres para su propio estado. Crea un directorio y\nescribe el archivo de composición:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">mkdir</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-p</span><span class=\"token plain\"> ~/llm-gateway </span><span class=\"token operator\" style=\"color:#393A34\">&amp;&amp;</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">cd</span><span class=\"token plain\"> ~/llm-gateway</span><br></div></code></pre></div></div>\n<div class=\"language-yaml codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">docker-compose.yml</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-yaml codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token key atrule\" style=\"color:#00a4db\">services</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">litellm</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">image</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> ghcr.io/berriai/litellm</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\">main</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">stable</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">container_name</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> llm</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">gateway</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">restart</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> unless</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">stopped</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">ports</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"127.0.0.1:4000:4000\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">volumes</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\"> ./config.yaml</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\">/app/config.yaml</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\">ro</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">command</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"--config\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"/app/config.yaml\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"--port\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"4000\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">env_file</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> .env</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">depends_on</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">db</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token key atrule\" style=\"color:#00a4db\">condition</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> service_healthy</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">db</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">image</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> postgres</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token number\" style=\"color:#36acaa\">17</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">container_name</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> llm</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">gateway</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">db</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">restart</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> unless</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">stopped</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">user</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"999:999\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">environment</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">POSTGRES_USER</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> llmproxy</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">POSTGRES_PASSWORD</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> $</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\">POSTGRES_PASSWORD</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">POSTGRES_DB</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> litellm</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">volumes</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\"> gateway</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">db</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\">/var/lib/postgresql/data</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">healthcheck</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">test</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"CMD-SHELL\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"pg_isready -U llmproxy -d litellm\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">interval</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> 5s</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">timeout</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> 5s</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">retries</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">10</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token key atrule\" style=\"color:#00a4db\">volumes</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  gateway</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">db</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><br></div></code></pre></div></div>\n<p>Cuatro de esas líneas están haciendo trabajo real:</p>\n<p><code>127.0.0.1:4000:4000</code> mantiene el gateway fuera de Internet público. Mantiene cada\ncredencial de modelo que posees, así que publicarlo en <code>0.0.0.0</code> pondría todas\ndetrás de una sola contraseña compartida. <a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/harden-docker-networks-multi-agent/\">Parte 1 de la serie de endurecimiento</a> es la versión larga de por qué\neso es un mal intercambio.</p>\n<p>La base de datos no tiene <code>ports:</code> en absoluto. Nada fuera de la red de composición necesita\nalcanzarla, y 5432 ya estaba ocupado.</p>\n<p><code>user: \"999:999\"</code> ejecuta Postgres como su usuario no root incorporado desde el primer arranque.\n<a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/harden-container-privilege-non-root/\">Parte 2</a> cubre lo que eso te ofrece\ny cómo encontrar el UID correcto para una imagen en lugar de adivinar.</p>\n<p><code>condition: service_healthy</code> importa más de lo que parece. El gateway ejecuta migraciones de base de datos al inicio: sin esto, compite con Postgres y se reinicia en bucle.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"informándole-sobre-los-modelos\">Informándole sobre los modelos<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-llm-gateway-wec-instance/#inform%C3%A1ndole-sobre-los-modelos\" class=\"hash-link\" aria-label=\"Enlace directo al Informándole sobre los modelos\" title=\"Enlace directo al Informándole sobre los modelos\" translate=\"no\">​</a></h2>\n<div class=\"language-yaml codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">config.yaml</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-yaml codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token key atrule\" style=\"color:#00a4db\">model_list</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\"> </span><span class=\"token key atrule\" style=\"color:#00a4db\">model_name</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> qwen</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">small</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">litellm_params</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">model</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> openai/Qwen2.5</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">3B</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">Instruct</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">api_base</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> https</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\">//inference.wiline.com/v1</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">api_key</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> os.environ/WEC_API_KEY</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\"> </span><span class=\"token key atrule\" style=\"color:#00a4db\">model_name</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> qwen</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">mid</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">litellm_params</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">model</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> openai/Qwen3.5</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">9B</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">api_base</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> https</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\">//inference.wiline.com/v1</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">api_key</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> os.environ/WEC_API_KEY</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\"> </span><span class=\"token key atrule\" style=\"color:#00a4db\">model_name</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> qwen</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">large</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">litellm_params</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">model</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> openai/Qwen3.5</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">122B</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">api_base</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> https</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\">//inference.wiline.com/v1</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">api_key</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> os.environ/WEC_API_KEY</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\"> </span><span class=\"token key atrule\" style=\"color:#00a4db\">model_name</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> embeddings</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">litellm_params</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">model</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> openai/bge</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">m3</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">api_base</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> https</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\">//inference.wiline.com/v1</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">api_key</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> os.environ/WEC_API_KEY</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token key atrule\" style=\"color:#00a4db\">general_settings</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">master_key</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> os.environ/LITELLM_MASTER_KEY</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">database_url</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> os.environ/DATABASE_URL</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">store_model_in_db</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token boolean important\" style=\"color:#36acaa\">true</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token key atrule\" style=\"color:#00a4db\">litellm_settings</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">drop_params</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token boolean important\" style=\"color:#36acaa\">true</span><br></div></code></pre></div></div>\n<p><code>model_name</code> es el alias que tus aplicaciones piden. <code>model:</code> es lo que el gateway realmente\nllama. Esa indirecta es la mayor parte del valor: cambia <code>qwen-mid</code> para apuntar a otro lugar\nel próximo mes y ningún cliente cambia.</p>\n<p>El prefijo <code>openai/</code> dice \"habla el protocolo OpenAI a un <code>api_base</code> personalizado.\" La\nAPI de Inferencia WEC es compatible con OpenAI, así que eso es todo lo que se necesita.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"secretos\">Secretos<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-llm-gateway-wec-instance/#secretos\" class=\"hash-link\" aria-label=\"Enlace directo al Secretos\" title=\"Enlace directo al Secretos\" translate=\"no\">​</a></h2>\n<p>Genera las contraseñas en lugar de inventarlas:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token assign-left variable\" style=\"color:#36acaa\">PGPW</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token variable\" style=\"color:#36acaa\">$(</span><span class=\"token variable\" style=\"color:#36acaa\">openssl rand </span><span class=\"token variable parameter variable\" style=\"color:#36acaa\">-hex</span><span class=\"token variable\" style=\"color:#36acaa\"> </span><span class=\"token variable number\" style=\"color:#36acaa\">16</span><span class=\"token variable\" style=\"color:#36acaa\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token assign-left variable\" style=\"color:#36acaa\">MK</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token string\" style=\"color:#e3116c\">\"sk-</span><span class=\"token string variable\" style=\"color:#36acaa\">$(</span><span class=\"token string variable\" style=\"color:#36acaa\">openssl rand </span><span class=\"token string variable parameter variable\" style=\"color:#36acaa\">-hex</span><span class=\"token string variable\" style=\"color:#36acaa\"> </span><span class=\"token string variable number\" style=\"color:#36acaa\">20</span><span class=\"token string variable\" style=\"color:#36acaa\">)</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">cat</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> .env </span><span class=\"token operator\" style=\"color:#393A34\">&lt;&lt;</span><span class=\"token string\" style=\"color:#e3116c\">EOF</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">POSTGRES_PASSWORD=</span><span class=\"token string variable\" style=\"color:#36acaa\">${PGPW}</span><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">DATABASE_URL=postgresql://llmproxy:</span><span class=\"token string variable\" style=\"color:#36acaa\">${PGPW}</span><span class=\"token string\" style=\"color:#e3116c\">@db:5432/litellm</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">LITELLM_MASTER_KEY=</span><span class=\"token string variable\" style=\"color:#36acaa\">${MK}</span><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">WEC_API_KEY=your-wec-key-here</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">EOF</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">chmod</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">600</span><span class=\"token plain\"> .env</span><br></div></code></pre></div></div>\n<p>Luego pon tu verdadera clave WEC en lugar de <code>your-wec-key-here</code>. La clave maestra es la\ncredencial raíz del gateway: puede crear claves, eliminar claves y acceder a cada modelo,\nasí que va en tu gestor de contraseñas y en ningún otro lugar.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"levantándolo\">Levantándolo<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-llm-gateway-wec-instance/#levant%C3%A1ndolo\" class=\"hash-link\" aria-label=\"Enlace directo al Levantándolo\" title=\"Enlace directo al Levantándolo\" translate=\"no\">​</a></h2>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose up </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"> ✔ Imagen ghcr.io/berriai/litellm:main-stable Descargada          58.2s</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"> ✔ Red llm-gateway_default               Creada          0.1s</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"> ✔ Volumen llm-gateway_gateway-db             Creado          0.1s</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"> ✔ Contenedor llm-gateway-db                  Saludable          8.8s</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"> ✔ Contenedor llm-gateway                     Creado          0.2s</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"docker compose up descargando la imagen y creando ambos contenedores\" src=\"https://wec.wiline.com/docs/es/assets/images/gw-compose-up-e741e826f043d86aa7f1a7214f71fbcb.png\" width=\"1066\" height=\"298\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 1.</strong> Una descarga, dos contenedores, una red y un volumen.</p>\n<p><code>Creado</code> en lugar de <code>Iniciado</code> en esa última línea es normal para la salida de composición,\npero verifica de todos modos — y verifica cuánto te costó la imagen en disco mientras estás ahí:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose </span><span class=\"token function\" style=\"color:#d73a49\">ps</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-a</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&amp;&amp;</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">echo</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&amp;&amp;</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">df</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-h</span><span class=\"token plain\"> /</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">NAME             IMAGE                                 SERVICE   STATUS                   PORTS</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">llm-gateway      ghcr.io/berriai/litellm:main-stable   litellm   Up 3 minutes             127.0.0.1:4000-&gt;4000/tcp</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">llm-gateway-db   postgres:17                           db        Up 3 minutes (healthy)   5432/tcp</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Filesystem      Size  Used Avail Use% Mounted on</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">/dev/vda1        58G   54G  4.2G  93% /</span><br></div></code></pre></div></div>\n<p>Nota la columna <code>PORTS</code> de la base de datos: <code>5432/tcp</code> sin enlace de host delante de\nella. La imagen del gateway es de 1.16GB, lo cual en una caja que ya ejecuta cinco pilas no es\nnada despreciable. Verifica que tienes espacio antes de comenzar, no después.</p>\n<p>Dos líneas en el registro de inicio valen la pena leer, porque ambas se ven peores de lo que son:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose logs litellm </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--tail</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">40</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">✅ Diferencia de migración aplicada con éxito</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">INFO:     Inicio de la aplicación completo.</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">INFO:     Uvicorn ejecutándose en http://0.0.0.0:4000 (Presiona CTRL+C para salir)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">register_model: model=openai/Qwen2.5-3B-Instruct no en el mapa de costos incorporado y no</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">se coincidió con ninguna variante de prefijo/región; los campos de costo de caché se establecerán en 0.</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Estado del contenedor, uso de disco y el registro de inicio con migraciones y advertencias del mapa de costos\" src=\"https://wec.wiline.com/docs/es/assets/images/gw-ps-logs-ed50299979281a4fb3dfd6ec9c89d189.png\" width=\"2496\" height=\"1408\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 2.</strong> Migraciones aplicadas, inicio de la aplicación completo, y una advertencia del mapa de costos por modelo.</p>\n<p><code>0.0.0.0:4000</code> es el enlace <em>dentro</em> del contenedor. La publicación del host sigue siendo\n<code>127.0.0.1</code>, que es lo que realmente controla el acceso. Y la advertencia del mapa de costos es\nmás específica de lo que parece: se trata de la fijación de precios de caché específicamente. La\nfijación de precios de tokens regulares sigue ocurriendo, lo cual resulta ser importante más adelante.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"la-primera-llamada\">La primera llamada<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-llm-gateway-wec-instance/#la-primera-llamada\" class=\"hash-link\" aria-label=\"Enlace directo al La primera llamada\" title=\"Enlace directo al La primera llamada\" translate=\"no\">​</a></h2>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token builtin class-name\">set</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-a</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&amp;&amp;</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">.</span><span class=\"token plain\"> ./.env </span><span class=\"token operator\" style=\"color:#393A34\">&amp;&amp;</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">set</span><span class=\"token plain\"> +a</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-s</span><span class=\"token plain\"> http://127.0.0.1:4000/v1/chat/completions </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Authorization: Bearer </span><span class=\"token string variable\" style=\"color:#36acaa\">$LITELLM_MASTER_KEY</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Content-Type: application/json\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'{\"model\":\"qwen-mid\",\"messages\":[{\"role\":\"user\",\"content\":\"Responde exactamente: gateway works\"}],\"max_tokens\":20}'</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> jq</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">\"finish_reason\": \"length\",</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">\"message\": {</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  \"reasoning_content\": \"Proceso de Pensamiento:\\n\\n1.  **Analiza la Solicitud:**\\n    *   Entrada: \\\"\",</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  \"content\": null</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">}</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">\"usage\": {\"completion_tokens\": 20, \"prompt_tokens\": 16, \"total_tokens\": 36}</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Respuesta cruda con finish_reason length, reasoning_content poblado y content null\" src=\"https://wec.wiline.com/docs/es/assets/images/gw-reasoning-null-2d60fce2efcb8e175da27120082ac585.png\" width=\"2480\" height=\"1094\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 3.</strong> Una llamada exitosa sin nada en ella.</p>\n<p><code>content: null</code>. La llamada funcionó: se enruta, llegó al backend, contó tokens\n— pero no hay respuesta en ella.</p>\n<p><code>Qwen3.5-9B</code> es un modelo de razonamiento. Gastó todos los veinte tokens pensando y no le quedó\nnada para hablar. El movimiento obvio es darle más espacio:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-s</span><span class=\"token plain\"> http://127.0.0.1:4000/v1/chat/completions </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Authorization: Bearer </span><span class=\"token string variable\" style=\"color:#36acaa\">$LITELLM_MASTER_KEY</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Content-Type: application/json\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'{\"model\":\"qwen-mid\",\"messages\":[{\"role\":\"user\",\"content\":\"Responde exactamente: gateway works\"}],\"max_tokens\":200}'</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> jq </span><span class=\"token string\" style=\"color:#e3116c\">'.choices[0].message.content, .usage'</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">null</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">{</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  \"completion_tokens\": 200,</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  \"prompt_tokens\": 16,</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  \"total_tokens\": 216</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">}</span><br></div></code></pre></div></div>\n<p>Doscientos tokens, aún nada. Y esta es la parte que vale la pena pausar: una ejecución anterior de ese mismo\ncomando <em>sí</em> respondió, con 192 tokens. Mismo modelo, mismo aviso,\ndiferente cantidad de pensamiento. No hay un <code>max_tokens</code> que puedas establecer que garantice\nuna respuesta, porque la longitud del razonamiento no es fija.</p>\n<p>Ahora el mismo aviso a través del modelo pequeño, con los veinte tokens originales:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-s</span><span class=\"token plain\"> http://127.0.0.1:4000/v1/chat/completions </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Authorization: Bearer </span><span class=\"token string variable\" style=\"color:#36acaa\">$LITELLM_MASTER_KEY</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Content-Type: application/json\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'{\"model\":\"qwen-small\",\"messages\":[{\"role\":\"user\",\"content\":\"Responde exactamente: gateway works\"}],\"max_tokens\":20}'</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> jq </span><span class=\"token string\" style=\"color:#e3116c\">'.choices[0].message.content, .usage'</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">\"gateway works\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">{</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  \"completion_tokens\": 3,</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  \"prompt_tokens\": 35,</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  \"total_tokens\": 38</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">}</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Modelo de razonamiento quemando 200 tokens de finalización y devolviendo null, modelo pequeño respondiendo en 3\" src=\"https://wec.wiline.com/docs/es/assets/images/gw-token-contrast-922bab5aa94a6417e02dbc0d6fe0c874.png\" width=\"1854\" height=\"608\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 4.</strong> Doscientos tokens y ninguna respuesta, contra tres tokens y la respuesta.</p>\n<p>Nada está roto aquí. El modelo de razonamiento está haciendo exactamente lo que se supone que debe hacer,\nen una pregunta que no lo necesitaba. La solución no es un presupuesto de tokens más grande: no\nenviar trabajo trivial a ese modelo en primer lugar. Lo cual es todo el argumento\npara el enrutamiento, y el tema de la próxima publicación.</p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Habilidad desbloqueada 🏅</div><div class=\"admonitionContent_BuS1\"><p>Puedes poner un gateway frente a varios modelos, dar a cada uno un alias que tus aplicaciones\nllamen en lugar de un ID de modelo de proveedor, y leer una respuesta lo suficientemente bien como para distinguir un\nmodelo de razonamiento de uno simple.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"demostrando-que-la-puerta-está-cerrada\">Demostrando que la puerta está cerrada<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-llm-gateway-wec-instance/#demostrando-que-la-puerta-est%C3%A1-cerrada\" class=\"hash-link\" aria-label=\"Enlace directo al Demostrando que la puerta está cerrada\" title=\"Enlace directo al Demostrando que la puerta está cerrada\" translate=\"no\">​</a></h2>\n<p>Un gateway que mantiene cada credencial que posees debería rechazar a cualquiera sin una clave.\nVale la pena verificar en lugar de asumir:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-s</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-o</span><span class=\"token plain\"> /dev/null </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-w</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"sin clave:     %{http_code}</span><span class=\"token string entity\" style=\"color:#36acaa\">\\n</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"> http://127.0.0.1:4000/v1/chat/completions </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Content-Type: application/json\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'{\"model\":\"qwen-mid\",\"messages\":[{\"role\":\"user\",\"content\":\"hola\"}]}'</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-s</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-o</span><span class=\"token plain\"> /dev/null </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-w</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"clave incorrecta:  %{http_code}</span><span class=\"token string entity\" style=\"color:#36acaa\">\\n</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"> http://127.0.0.1:4000/v1/chat/completions </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Authorization: Bearer sk-not-a-real-key\"</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Content-Type: application/json\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'{\"model\":\"qwen-mid\",\"messages\":[{\"role\":\"user\",\"content\":\"hola\"}]}'</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-s</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-o</span><span class=\"token plain\"> /dev/null </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-w</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"clave maestra: %{http_code}</span><span class=\"token string entity\" style=\"color:#36acaa\">\\n</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"> http://127.0.0.1:4000/v1/chat/completions </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Authorization: Bearer </span><span class=\"token string variable\" style=\"color:#36acaa\">$LITELLM_MASTER_KEY</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Content-Type: application/json\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'{\"model\":\"qwen-mid\",\"messages\":[{\"role\":\"user\",\"content\":\"hola\"}],\"max_tokens\":5}'</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">sin clave:     401</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">clave incorrecta:  401</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">clave maestra: 200</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Tres llamadas curl devolviendo 401, 401 y 200\" src=\"https://wec.wiline.com/docs/es/assets/images/gw-auth-401-a9d9d89ff8432a6ec7a29486800ea82c.png\" width=\"1854\" height=\"302\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 5.</strong> Tanto anónimos como falsificados rechazados, la clave real a través.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"la-interfaz-de-administración\">La interfaz de administración<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-llm-gateway-wec-instance/#la-interfaz-de-administraci%C3%B3n\" class=\"hash-link\" aria-label=\"Enlace directo al La interfaz de administración\" title=\"Enlace directo al La interfaz de administración\" translate=\"no\">​</a></h2>\n<p>El gateway incluye una interfaz web, y está vinculada a localhost — que es el\npunto, así que accede a ella a través de SSH en lugar de abrir un puerto:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">ssh</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-L</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">4000</span><span class=\"token plain\">:127.0.0.1:4000 ubuntu@your-wec-instance</span><br></div></code></pre></div></div>\n<p>Deja eso funcionando y abre <code>http://localhost:4000/ui</code>. Nombre de usuario <code>admin</code>, la contraseña\nes la clave maestra de tu <code>.env</code>.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"La pantalla de inicio de sesión del gateway, pidiendo admin más la clave maestra\" src=\"https://wec.wiline.com/docs/es/assets/images/gw-ui-login-94d370f9590bd022684fb80ccabb2aff.png\" width=\"2508\" height=\"1458\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 6.</strong> La página de inicio de sesión indica sus propias credenciales predeterminadas, lo cual es un buen\nrecordatorio de que la clave maestra es lo único que está ahí.</p>\n<p>Haz clic en <strong>Modelos + Endpoints</strong>:</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Gestión de modelos listando qwen-small, qwen-mid, qwen-large y embeddings, todos mostrando costo cero\" src=\"https://wec.wiline.com/docs/es/assets/images/gw-ui-models-4372c39b1bfb45a2d61782e145f1c5e2.png\" width=\"2510\" height=\"1464\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 7.</strong> Los cuatro alias registrados, cada uno mapeado a su objetivo <code>openai/...</code>.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"claves-que-no-pueden-hacer-todo\">Claves que no pueden hacer todo<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-llm-gateway-wec-instance/#claves-que-no-pueden-hacer-todo\" class=\"hash-link\" aria-label=\"Enlace directo al Claves que no pueden hacer todo\" title=\"Enlace directo al Claves que no pueden hacer todo\" translate=\"no\">​</a></h2>\n<p>La clave maestra puede acceder a cada modelo y crear más claves. Ninguna aplicación debería\ntenerla nunca. En su lugar, emite una clave virtual específica para lo que esa aplicación realmente necesita.</p>\n<p><strong>Claves Virtuales</strong> → <strong>+ Crear Nueva Clave</strong>:</p>\n<table><thead><tr><th>Campo</th><th>Valor</th></tr></thead><tbody><tr><td>Propietario</td><td>Tú</td></tr><tr><td>Equipo</td><td>dejar vacío</td></tr><tr><td>Nombre de Clave</td><td><code>demo-app</code></td></tr><tr><td>Modelos</td><td><code>qwen-small</code></td></tr><tr><td>Presupuesto Máximo (USD)</td><td><code>0.10</code></td></tr><tr><td>Restablecer Presupuesto</td><td>diario</td></tr></tbody></table>\n<p>Dos cosas sobre este formulario. <strong>Presupuesto Máximo</strong> vive bajo <strong>Configuraciones Opcionales</strong>,\nque comienza colapsada — fácil de perder y luego preguntarse dónde fue el campo de presupuesto. Y\ndejar <strong>Modelos</strong> vacío significa <em>todos los modelos</em>, lo opuesto a lo que quieres aquí.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El formulario de creación de clave con qwen-small seleccionado y un presupuesto diario de diez centavos\" src=\"https://wec.wiline.com/docs/es/assets/images/gw-key-create-f440427f7d6f55ad01482ed469ff3624.png\" width=\"2510\" height=\"1460\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 8.</strong> Específico para un modelo, limitado a diez centavos al día.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El diálogo Guardar tu Clave, advirtiendo que la clave no puede ser vista nuevamente\" src=\"https://wec.wiline.com/docs/es/assets/images/gw-key-saved-e13c0abfcd4b4c0bfefa791764eaae50.png\" width=\"2560\" height=\"1476\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 9.</strong> El diálogo lo dice claramente. Créelo.</p>\n<div class=\"theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z\"></path></svg></span>Copie la clave ahora</div><div class=\"admonitionContent_BuS1\"><p>La clave generada se muestra una vez y nunca más. Cierra esto sin copiar y\ntu única opción es eliminar la clave y crear otra.</p></div></div>\n<p>Ahora la parte que justifica todo el ejercicio. La misma clave, dos modelos:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-s</span><span class=\"token plain\"> http://127.0.0.1:4000/v1/chat/completions </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Authorization: Bearer </span><span class=\"token string variable\" style=\"color:#36acaa\">$VK</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Content-Type: application/json\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'{\"model\":\"qwen-small\",\"messages\":[{\"role\":\"user\",\"content\":\"di hola\"}],\"max_tokens\":20}'</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> jq </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-r</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'.choices[0].message.content'</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">¡Hola! ¿Cómo puedo ayudarte hoy?</span><br></div></code></pre></div></div>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-s</span><span class=\"token plain\"> http://127.0.0.1:4000/v1/chat/completions </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Authorization: Bearer </span><span class=\"token string variable\" style=\"color:#36acaa\">$VK</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Content-Type: application/json\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'{\"model\":\"qwen-large\",\"messages\":[{\"role\":\"user\",\"content\":\"di hola\"}],\"max_tokens\":20}'</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> jq</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">{</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  \"error\": {</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    \"message\": \"clave no permitida para acceder al modelo. Esta clave solo puede acceder a modelos=['qwen-small']. Intentó acceder a qwen-large\",</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    \"type\": \"key_model_access_denied\",</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    \"param\": \"model\",</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    \"code\": \"403\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">}</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"La misma clave virtual respondiendo en qwen-small y siendo rechazada con un 403 en qwen-large\" src=\"https://wec.wiline.com/docs/es/assets/images/gw-key-403-ff8ff7ee956b8b081dd67c4d3834ac10.png\" width=\"1858\" height=\"508\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 10.</strong> Una credencial, un modelo. Filtrarla y el radio de explosión es esa línea.</p>\n<p>Compara eso con la situación en la que comenzamos: una clave en cinco archivos <code>.env</code>, con acceso\na todos los trece modelos y sin forma de saber qué aplicación la está utilizando.</p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Habilidad desbloqueada 🏅</div><div class=\"admonitionContent_BuS1\"><p>Puedes emitir una credencial específica por aplicación, restringirla a modelos específicos\ncon un límite de gasto, y revocarla por sí sola sin tocar nada más.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"lo-que-el-gateway-registró\">Lo que el gateway registró<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-llm-gateway-wec-instance/#lo-que-el-gateway-registr%C3%B3\" class=\"hash-link\" aria-label=\"Enlace directo al Lo que el gateway registró\" title=\"Enlace directo al Lo que el gateway registró\" translate=\"no\">​</a></h2>\n<p>Cada llamada aterriza en un registro de gastos:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-s</span><span class=\"token plain\"> http://127.0.0.1:4000/spend/logs </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Authorization: Bearer </span><span class=\"token string variable\" style=\"color:#36acaa\">$LITELLM_MASTER_KEY</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> jq </span><span class=\"token string\" style=\"color:#e3116c\">'[.[] | select(.spend &gt; 0)] | .[-3:] | .[] | {model, spend, prompt_tokens, completion_tokens}'</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">{</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  \"model\": \"openai/Qwen3.5-9B\",</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  \"spend\": 8.520000000000001e-05,</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  \"prompt_tokens\": 16,</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  \"completion_tokens\": 20</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">}</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">{</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  \"model\": \"openai/Qwen3.5-9B\",</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  \"spend\": 2.62e-05,</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  \"prompt_tokens\": 11,</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  \"completion_tokens\": 5</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">}</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Filas del registro de gastos mostrando conteos de tokens y una cifra en dólares por llamada\" src=\"https://wec.wiline.com/docs/es/assets/images/gw-spend-logs-dfdc834679299ecd1fcfcc774e83b5b3.png\" width=\"1856\" height=\"684\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 11.</strong> Tokens contados por llamada, con un precio adjunto a cada uno.</p>\n<p>Ese <code>select(.spend &gt; 0)</code> no es cosmético. Las llamadas fallidas también se registran, con cero\ntokens y cero gasto: los 401 de antes están todos ahí con\n<code>status: \"failure\"</code>. Útil por sí mismo: el registro de gastos también sirve como un registro de\nalguien intentando claves que no funcionan.</p>\n<p>Los conteos de tokens son reales. Las cifras en dólares no lo son — no para ti, de todos modos. Estos son\nmodelos autoalojados en tu propia infraestructura; el gateway los está fijando precios a partir de\nuna tabla incorporada de tarifas de API públicas. El número es preciso y confiablemente incorrecto,\nlo cual es peor que en blanco. Arreglar eso significa declarar tu propio costo por token, y\nese es el tema de la cuarta publicación de esta serie.</p>\n<p>Dos cosas más en la entrada completa del registro, ambas útiles:</p>\n<div class=\"language-json codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-json codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token property\" style=\"color:#36acaa\">\"litellm_overhead_time_ms\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">36.189</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token property\" style=\"color:#36acaa\">\"messages\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token property\" style=\"color:#36acaa\">\"response\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><br></div></code></pre></div></div>\n<p>El gateway mide su propia sobrecarga y la informa por solicitud: 36ms aquí, que\nes un número real al que puedes exigirle. Y los avisos y respuestas no se almacenan por\ndefecto: el registro sabe que ocurrió una llamada y cuánto costó, no lo que se dijo.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"solución-de-problemas\">Solución de problemas<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-llm-gateway-wec-instance/#soluci%C3%B3n-de-problemas\" class=\"hash-link\" aria-label=\"Enlace directo al Solución de problemas\" title=\"Enlace directo al Solución de problemas\" translate=\"no\">​</a></h2>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"el-contenedor-del-gateway-se-inicia-y-sale-inmediatamente\">El contenedor del gateway se inicia y sale inmediatamente<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-llm-gateway-wec-instance/#el-contenedor-del-gateway-se-inicia-y-sale-inmediatamente\" class=\"hash-link\" aria-label=\"Enlace directo al El contenedor del gateway se inicia y sale inmediatamente\" title=\"Enlace directo al El contenedor del gateway se inicia y sale inmediatamente\" translate=\"no\">​</a></h3>\n<p>Casi siempre es la base de datos. Verifica <code>docker compose logs litellm</code> para errores de migración: si el gateway se levantó antes de que Postgres estuviera listo, falta la condición <code>depends_on</code>\no el healthcheck no está pasando.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"content-regresa-null-con-finish_reason-length\"><code>content</code> regresa <code>null</code> con <code>finish_reason: \"length\"</code><a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-llm-gateway-wec-instance/#content-regresa-null-con-finish_reason-length\" class=\"hash-link\" aria-label=\"Enlace directo al content-regresa-null-con-finish_reason-length\" title=\"Enlace directo al content-regresa-null-con-finish_reason-length\" translate=\"no\">​</a></h3>\n<p>El modelo es un modelo de razonamiento y utilizó todo tu presupuesto de tokens pensando. Aumentar\n<code>max_tokens</code> ayuda pero no garantiza nada: el mismo aviso con 200 tokens respondió en una ejecución y devolvió <code>null</code> en la siguiente. Para trabajos que no necesitan\nrazonamiento, envíalos a un modelo que no lo haga.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"la-interfaz-de-administración-no-carga\">La interfaz de administración no carga<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-llm-gateway-wec-instance/#la-interfaz-de-administraci%C3%B3n-no-carga\" class=\"hash-link\" aria-label=\"Enlace directo al La interfaz de administración no carga\" title=\"Enlace directo al La interfaz de administración no carga\" translate=\"no\">​</a></h3>\n<p>Está vinculada a <code>127.0.0.1</code>, así que es inaccesible desde cualquier lugar que no sea la caja misma.\nUsa un túnel SSH. Si el túnel está activo y la página aún no carga, confirma que el\npuerto en <code>docker compose ps</code> coincide con el que has reenviado.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"una-cadena-de-shell-no-hizo-nada-en-silencio\">Una cadena de shell no hizo nada en silencio<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-llm-gateway-wec-instance/#una-cadena-de-shell-no-hizo-nada-en-silencio\" class=\"hash-link\" aria-label=\"Enlace directo al Una cadena de shell no hizo nada en silencio\" title=\"Enlace directo al Una cadena de shell no hizo nada en silencio\" translate=\"no\">​</a></h3>\n<p>Las cadenas unidas con <code>&amp;&amp;</code> se detienen en el primer comando que falla, y un <code>grep</code> fallido\ncuenta como un fallo. Sourcing un archivo env cuyo nombre de variable adivinaste mal\ndeja un marcador en su lugar y todo parece bien hasta un error de autenticación tres\npasos después. Verifica los marcadores explícitamente:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">grep</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'your-wec-key-here'</span><span class=\"token plain\"> .env</span><br></div></code></pre></div></div>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"eliminando-una-clave-virtual\">Eliminando una clave virtual<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-llm-gateway-wec-instance/#eliminando-una-clave-virtual\" class=\"hash-link\" aria-label=\"Enlace directo al Eliminando una clave virtual\" title=\"Enlace directo al Eliminando una clave virtual\" translate=\"no\">​</a></h3>\n<p>Desde la terminal, por alias:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-s</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-X</span><span class=\"token plain\"> POST http://127.0.0.1:4000/key/delete </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Authorization: Bearer </span><span class=\"token string variable\" style=\"color:#36acaa\">$LITELLM_MASTER_KEY</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Content-Type: application/json\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'{\"key_aliases\":[\"demo-app\"]}'</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> jq</span><br></div></code></pre></div></div>\n<div class=\"skillComplete\"><span class=\"skillComplete__medal\" aria-hidden=\"true\">🎯</span><div class=\"skillComplete__text\"><div class=\"skillComplete__title\">Finished this tutorial?</div><div class=\"skillComplete__sub\">Mark it complete to earn <strong>One endpoint, scoped keys</strong> on your skill path.</div></div><button type=\"button\" class=\"skillComplete__btn\">Mark as complete ✓</button></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"qué-sigue\">Qué sigue<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-llm-gateway-wec-instance/#qu%C3%A9-sigue\" class=\"hash-link\" aria-label=\"Enlace directo al Qué sigue\" title=\"Enlace directo al Qué sigue\" translate=\"no\">​</a></h2>\n<p>Ahora tienes un endpoint, una credencial por aplicación y un registro de cada llamada. La\npregunta obvia es la que planteó la Figura 4: si el modelo pequeño responde en 3 tokens\nlo que el modelo de razonamiento gasta 200 sin responder en absoluto, ¿por qué se está enrutando\ncualquier cosa al costoso por defecto?</p>\n<p>La próxima publicación pone al gateway a cargo de esa decisión y mide lo que cuesta pedirle.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"lectura-adicional\">Lectura adicional<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-llm-gateway-wec-instance/#lectura-adicional\" class=\"hash-link\" aria-label=\"Enlace directo al Lectura adicional\" title=\"Enlace directo al Lectura adicional\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\"><a href=\"https://docs.docker.com/reference/compose-file/services/#depends_on\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Docker Compose — dependencias de servicio y healthchecks</a></li>\n<li class=\"\"><a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/harden-docker-networks-multi-agent/\">Endurecer redes Docker para hosts multi-agente</a></li>\n<li class=\"\"><a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/harden-container-privilege-non-root/\">Root por defecto: endurecimiento del privilegio del contenedor</a></li>\n</ul>",
            "url": "https://wec.wiline.com/docs/es/tutorials/deploy-llm-gateway-wec-instance/",
            "title": "Un endpoint, muchos modelos: despliega un gateway LLM en una instancia WEC",
            "summary": "Cada aplicación en una caja que tiene la misma clave API es un problema esperando a suceder. Un gateway soluciona eso: un endpoint, claves específicas por aplicación, presupuestos por clave y un registro de quién gastó qué. Desplegado de verdad en un host que ya ejecuta cinco otras pilas, incluyendo las partes que nos sorprendieron.",
            "date_modified": "2026-08-13T00:00:00.000Z",
            "author": {
                "name": "Rafael Fernandes",
                "url": "https://www.linkedin.com/in/rafaelmacariofernandes/"
            },
            "tags": [
                "llm",
                "gateway",
                "docker",
                "autoalojamiento",
                "api",
                "litellm",
                "wec"
            ]
        },
        {
            "id": "https://wec.wiline.com/docs/es/tutorials/harden-container-privilege-non-root/",
            "content_html": "<div class=\"tutorialHero\"><svg viewBox=\"0 0 24 24\" fill=\"#2496ED\" aria-label=\"Docker\" class=\"tutorialHero__docker\"><path d=\"M13.983 11.078h2.119a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.119a.185.185 0 00-.185.185v1.888c0 .102.083.185.185.185m-2.954-5.43h2.118a.186.186 0 00.186-.186V3.574a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m0 2.716h2.118a.187.187 0 00.186-.186V6.29a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.887c0 .102.082.185.185.186m-2.93 0h2.12a.186.186 0 00.184-.186V6.29a.185.185 0 00-.185-.185H8.1a.185.185 0 00-.185.185v1.887c0 .102.083.185.185.186m-2.964 0h2.119a.186.186 0 00.185-.186V6.29a.185.185 0 00-.185-.185H5.136a.186.186 0 00-.186.185v1.887c0 .102.084.185.186.186m5.893 2.715h2.118a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m-2.93 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.083.185.185.185m-2.964 0h2.119a.185.185 0 00.185-.185V9.006a.185.185 0 00-.184-.186h-2.12a.186.186 0 00-.186.186v1.887c0 .102.084.185.186.185m-2.92 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.082.185.185.185M23.763 9.89c-.065-.051-.672-.51-1.954-.51-.338.001-.676.03-1.01.087-.248-1.7-1.653-2.53-1.716-2.566l-.344-.199-.226.327c-.284.438-.49.922-.612 1.43-.23.97-.09 1.882.403 2.661-.595.332-1.55.413-1.744.42H.751a.751.751 0 00-.75.748 11.376 11.376 0 00.692 4.062c.545 1.428 1.355 2.48 2.41 3.124 1.18.723 3.1 1.137 5.275 1.137.983.003 1.963-.086 2.93-.266a12.248 12.248 0 003.823-1.389c.98-.567 1.86-1.288 2.61-2.136 1.252-1.418 1.998-2.997 2.553-4.4h.221c1.372 0 2.215-.549 2.68-1.009.309-.293.55-.65.707-1.046l.098-.288Z\"></path></svg><span class=\"tutorialHero__plus\">+</span><img class=\"tutorialHero__langfuse\" src=\"data:image/webp;base64,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\" alt=\"Langfuse\"></div>\n<div class=\"skillTracker\" tabindex=\"0\" aria-label=\"Skill path: 0 of 3 skills earned\"><div class=\"skillTracker__ring\" style=\"--skillTracker-pct:0%\"><span class=\"skillTracker__ringLabel\">0<!-- -->/<!-- -->3</span></div><div class=\"skillTracker__panel\"><div class=\"skillTracker__header\"><span class=\"skillTracker__title\">🎯 Skill path</span><span class=\"skillTracker__count\">0<!-- -->/<!-- -->3<!-- --> earned</span></div><div class=\"skillTracker__series\">Hardening self-hosted AI infra</div><ul class=\"skillTracker__steps\"><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/harden-docker-networks-multi-agent/\"><span class=\"skillTracker__dot\" data-state=\"locked\">1</span><span class=\"skillTracker__skill\" data-state=\"locked\">Lock down Docker networks</span></a></li><li><span class=\"skillTracker__step\"><span class=\"skillTracker__dot\" data-state=\"current\">2</span><span class=\"skillTracker__skill\" data-state=\"current\">Run containers as non-root</span></span></li><li><span class=\"skillTracker__step\"><span class=\"skillTracker__dot\" data-state=\"locked\">🏆</span><span class=\"skillTracker__skill\" data-state=\"locked\">Identity in front of every port</span></span></li></ul><div class=\"skillTracker__footer\">Finish this tutorial, then mark it complete below to earn the skill.</div></div></div>\n<p><a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/harden-docker-networks-multi-agent/\">Parte 1</a> de esta serie auditó una caja multi-agente en vivo y encontró cada pregunta sobre exposición de red que valía la pena hacer. Una línea de esa auditoría no se siguió: <em>\"el Postgres, ClickHouse y Redis detrás de Langfuse estaban publicados en <code>127.0.0.1</code> en lugar del mundo — alguien tomó una buena decisión allí. Mantén ese pensamiento.\"</em></p>\n<p>Aquí está la otra mitad de ese pensamiento: conseguir la red correcta no dice nada sobre lo que sucede <em>después</em> de que alguien ya esté dentro de un contenedor. Si el proceso que se ejecuta allí es root, un compromiso comienza con las llaves de todo el sistema de archivos. Así que verificamos — en la misma caja, la misma pila de Langfuse que la Parte 1 ya alabó — si \"red correcta\" también significaba \"privilegio correcto.\" No lo era, para dos de los seis contenedores. Aquí está cómo se veía realmente la solución, incluyendo la parte que se rompió.</p>\n<!-- -->\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"verificando-quién-es-realmente-root\">Verificando quién es realmente root<a href=\"https://wec.wiline.com/docs/es/tutorials/harden-container-privilege-non-root/#verificando-qui%C3%A9n-es-realmente-root\" class=\"hash-link\" aria-label=\"Enlace directo al Verificando quién es realmente root\" title=\"Enlace directo al Verificando quién es realmente root\" translate=\"no\">​</a></h2>\n<p>Docker no te dice esto por defecto — un contenedor puede estar publicado correctamente, saludable y ejecutándose como <code>root</code> todo el tiempo, sin que nada en <code>docker ps</code> lo sugiera. La única forma de saberlo es preguntar directamente al contenedor:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">exec</span><span class=\"token plain\"> langfuse-postgres-1 </span><span class=\"token function\" style=\"color:#d73a49\">id</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-u</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">exec</span><span class=\"token plain\"> langfuse-redis-1 </span><span class=\"token function\" style=\"color:#d73a49\">id</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-u</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">0</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">0</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Ambas verificaciones de id -u devolviendo 0 — ambos contenedores ejecutándose como root\" src=\"https://wec.wiline.com/docs/es/assets/images/privsec-root-confirmed-9af63f21bf8c07c7554a6cbab4f02929.png\" width=\"617\" height=\"96\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 1.</strong> La verificación que la mayoría de las configuraciones nunca ejecutan: ambas llamadas a <code>id -u</code> regresan <code>0</code>. Saludables, publicados correctamente, y root todo el tiempo.</p>\n<p><code>0</code> es root. Ambos estaban ejecutándose como root, y al mirar el archivo de composición se explica por qué — ninguno de los servicios tiene una directiva <code>user:</code> en absoluto:</p>\n<div class=\"language-yaml codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">docker-compose.yml (antes)</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-yaml codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">redis</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">image</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> docker.io/redis</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token number\" style=\"color:#36acaa\">7</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">restart</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> always</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">command</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">&gt;</span><span class=\"token scalar string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token scalar string\" style=\"color:#e3116c\">      --requirepass ${REDIS_AUTH:-myredissecret}</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token scalar string\" style=\"color:#e3116c\">      --maxmemory-policy noeviction</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:#393A34\">...</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">postgres</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">image</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> docker.io/postgres</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\">$</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\">POSTGRES_VERSION</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token number\" style=\"color:#36acaa\">-17</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">restart</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> always</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:#393A34\">...</span><br></div></code></pre></div></div>\n<p>Nadie decidió que estos debían ejecutarse como root. Es simplemente lo que sucede cuando no configuras nada — la misma forma que el hallazgo de la Parte 1 de que una contraseña de Redis no configurada predetermina la falta de autenticación. El servicio <code>clickhouse</code> en el mismo archivo muestra la solución ya medio hecha, sentada justo allí para comparación:</p>\n<div class=\"language-yaml codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">docker-compose.yml (ya correcto)</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-yaml codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">clickhouse</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">image</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> docker.io/clickhouse/clickhouse</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">server</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">restart</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> always</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">user</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"101:101\"</span><br></div></code></pre></div></div>\n<p>Una línea. Alguien lo configuró para ClickHouse y nunca llegó a los otros dos.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"encontrando-el-usuario-correcto-no-una-suposición\">Encontrando el usuario correcto, no una suposición<a href=\"https://wec.wiline.com/docs/es/tutorials/harden-container-privilege-non-root/#encontrando-el-usuario-correcto-no-una-suposici%C3%B3n\" class=\"hash-link\" aria-label=\"Enlace directo al Encontrando el usuario correcto, no una suposición\" title=\"Enlace directo al Encontrando el usuario correcto, no una suposición\" translate=\"no\">​</a></h2>\n<p>No inventes un UID — ambas imágenes oficiales ya incluyen un usuario no root construido para exactamente esto, y adivinar mal significa un error de permisos o, peor aún, un UID que silenciosamente no coincide con el que los propios archivos de la imagen son propiedad:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">exec</span><span class=\"token plain\"> langfuse-postgres-1 </span><span class=\"token function\" style=\"color:#d73a49\">id</span><span class=\"token plain\"> postgres</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">exec</span><span class=\"token plain\"> langfuse-redis-1 </span><span class=\"token function\" style=\"color:#d73a49\">id</span><span class=\"token plain\"> redis</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">uid=999(postgres) gid=999(postgres) groups=999(postgres),101(ssl-cert)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">uid=999(redis) gid=999(redis) groups=999(redis)</span><br></div></code></pre></div></div>\n<p>Ambas imágenes utilizan <code>999:999</code> para su usuario no root incorporado. Agrégalo al archivo de composición:</p>\n<div class=\"language-yaml codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">docker-compose.yml (después)</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-yaml codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">redis</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">image</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> docker.io/redis</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token number\" style=\"color:#36acaa\">7</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">user</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"999:999\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">restart</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> always</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:#393A34\">...</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">postgres</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">image</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> docker.io/postgres</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\">$</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\">POSTGRES_VERSION</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token number\" style=\"color:#36acaa\">-17</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">user</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"999:999\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">restart</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> always</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:#393A34\">...</span><br></div></code></pre></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"la-conversión-en-sí-fue-un-no-evento\">La conversión en sí fue un no evento<a href=\"https://wec.wiline.com/docs/es/tutorials/harden-container-privilege-non-root/#la-conversi%C3%B3n-en-s%C3%AD-fue-un-no-evento\" class=\"hash-link\" aria-label=\"Enlace directo al La conversión en sí fue un no evento\" title=\"Enlace directo al La conversión en sí fue un no evento\" translate=\"no\">​</a></h2>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose up </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> --force-recreate postgres redis</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"> ✔ Contenedor langfuse-postgres-1 recreado</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"> ✔ Contenedor langfuse-redis-1 recreado</span><br></div></code></pre></div></div>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">sleep</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">5</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose </span><span class=\"token function\" style=\"color:#d73a49\">ps</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">exec</span><span class=\"token plain\"> langfuse-postgres-1 </span><span class=\"token function\" style=\"color:#d73a49\">id</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-u</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">exec</span><span class=\"token plain\"> langfuse-redis-1 </span><span class=\"token function\" style=\"color:#d73a49\">id</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-u</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">langfuse-postgres-1   Up 34 seconds (healthy)   127.0.0.1:5433-&gt;5432/tcp</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">langfuse-redis-1      Up 34 seconds (healthy)   127.0.0.1:6379-&gt;6379/tcp</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">999</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">999</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"docker compose ps mostrando ambos contenedores saludables, y id -u ahora devolviendo 999\" src=\"https://wec.wiline.com/docs/es/assets/images/privsec-nonroot-healthy-2aa6142e51931837aa80ee308e8be448.png\" width=\"1584\" height=\"384\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 2.</strong> Los mismos dos contenedores, segundos después de <code>--force-recreate</code>: ambos <code>healthy</code>, ambos <code>id -u</code> ahora <code>999</code> en lugar de <code>0</code>.</p>\n<p>Sin errores de permisos, sin dramas de propiedad, ambos saludables en segundos. Eso vale la pena decirlo claramente porque no siempre es cierto — si estos volúmenes se hubieran inicializado como root en algún lugar upstream y nunca se les hubiera hecho <code>chown</code>, este comando exacto puede fallar con <code>permission denied</code> en el directorio de datos. No falló aquí, pero revisa tus propios registros después de recrear, no asumas que \"sin salida\" significa \"sin problema.\"</p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Habilidad desbloqueada 🏅</div><div class=\"admonitionContent_BuS1\"><p>Puedes verificar si un contenedor en ejecución es realmente root (<code>docker exec &lt;container&gt; id -u</code>), encontrar el usuario no root previsto de su imagen en lugar de adivinar un UID (<code>docker exec &lt;container&gt; id &lt;username&gt;</code>), y convertirlo con una línea <code>user:</code> en la composición.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"la-parte-que-realmente-se-rompió\">La parte que realmente se rompió<a href=\"https://wec.wiline.com/docs/es/tutorials/harden-container-privilege-non-root/#la-parte-que-realmente-se-rompi%C3%B3\" class=\"hash-link\" aria-label=\"Enlace directo al La parte que realmente se rompió\" title=\"Enlace directo al La parte que realmente se rompió\" translate=\"no\">​</a></h2>\n<p>Todo lo anterior fue limpio. Luego, los registros de un contenedor completamente diferente contaron una historia diferente:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> logs langfuse-langfuse-worker-1 </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--tail</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">20</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Error: getaddrinfo ENOTFOUND redis</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    at GetAddrInfoReqWrap.onlookupall [as oncomplete] (node:dns:122:26)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Error de Redis getaddrinfo ENOTFOUND redis</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Error de Redis connect ECONNREFUSED 172.19.0.5:6379</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">El trabajo de cola mixpanel-integration-processing-queue falló: Error: Tiempo de espera de socket.</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Esperando datos, pero no se recibió ninguno en 30000ms.</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">El trabajo de cola trace-delete falló: Error: Tiempo de espera de socket. Esperando datos, pero no se recibió ninguno en 30000ms.</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"registros de langfuse-worker mostrando repetidos tiempos de espera de socket de 30 segundos contra Redis, uno por cola\" src=\"https://wec.wiline.com/docs/es/assets/images/privsec-worker-broken-e776f29e677755bd8a5e61864dbe22bc.png\" width=\"995\" height=\"428\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 3.</strong> Un contenedor que nadie tocó, fallando de todos modos — cada trabajo de cola con el mismo tiempo de espera de socket de 30 segundos contra Redis, mucho después de la recreación que lo causó.</p>\n<p><code>langfuse-worker</code> nunca tocó la propiedad de Postgres o Redis — solo mantiene una conexión activa a Redis, y <code>--force-recreate</code> derribó el contenedor al que apuntaba esa conexión. La propia verificación de salud de Docker decía que Redis estaba <code>healthy</code> de nuevo mucho antes de que el trabajador se rindiera al reintentar; ioredis simplemente no se reconecta limpiamente por sí mismo aquí, y siguió fallando con tiempos de espera de socket de 30 segundos mucho después de que la dependencia de la que dependía volvió.</p>\n<p>La solución es el reinicio del propio servicio dependiente, no otra mirada a Redis:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose restart langfuse-worker langfuse-web</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">2026-08-10T19:40:22.505Z info    La conexión a Redis se ha cerrado.</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">2026-08-10T19:40:22.769Z info    Apagado completo, saliendo del proceso...</span><br></div></code></pre></div></div>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">sleep</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">10</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> logs langfuse-langfuse-worker-1 </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--tail</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">15</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">experiment-create-queue executor started: true</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">posthog-integration-queue executor started: true</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">data-retention-queue executor started: true</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">webhook-queue executor started: true</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Listening: http://21caf9a28775:3030</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"registros de langfuse-worker después del reinicio, mostrando cada ejecutor de cola comenzando limpiamente\" src=\"https://wec.wiline.com/docs/es/assets/images/privsec-worker-recovered-ca07f68b5e4cb0994524f20c497f41ca.png\" width=\"994\" height=\"372\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 4.</strong> El mismo contenedor, un reinicio después — cada ejecutor de cola comienza limpio, sin errores de Redis en ninguna parte del registro.</p>\n<p>Inicio limpio de la cola, sin errores de Redis. Esa es toda la lección: <strong>endurecer un contenedor no se limita a ese contenedor.</strong> Cualquier cosa que mantenga una conexión activa con la cosa que acabas de recrear necesita su propio reinicio, a propósito, no como un pensamiento posterior que descubres a partir de un registro de errores veinte minutos después.</p>\n<div class=\"theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z\"></path></svg></span>Esto es lo que debes recordar</div><div class=\"admonitionContent_BuS1\"><p><code>--force-recreate</code> en una dependencia compartida (una base de datos, una cola, un caché) puede romper silenciosamente cada servicio que ya tenía una conexión abierta a ella — incluso después de que la dependencia misma informe que está saludable de nuevo. Reinicia explícitamente a los dependientes; no asumas que lo notarán por sí mismos.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"lo-que-encontramos-y-lo-que-costó-arreglar\">Lo que encontramos y lo que costó arreglar<a href=\"https://wec.wiline.com/docs/es/tutorials/harden-container-privilege-non-root/#lo-que-encontramos-y-lo-que-cost%C3%B3-arreglar\" class=\"hash-link\" aria-label=\"Enlace directo al Lo que encontramos y lo que costó arreglar\" title=\"Enlace directo al Lo que encontramos y lo que costó arreglar\" translate=\"no\">​</a></h2>\n<table><thead><tr><th>Contenedor</th><th>Antes</th><th>Solución</th><th>¿Rompe algo?</th></tr></thead><tbody><tr><td><code>langfuse-postgres-1</code></td><td>root (<code>uid 0</code>)</td><td><code>user: \"999:999\"</code></td><td>No — recreado limpio</td></tr><tr><td><code>langfuse-redis-1</code></td><td>root (<code>uid 0</code>)</td><td><code>user: \"999:999\"</code></td><td>No — recreado limpio</td></tr><tr><td><code>langfuse-clickhouse-1</code></td><td>ya <code>101:101</code></td><td>ninguna necesaria</td><td>—</td></tr><tr><td><code>langfuse-langfuse-worker-1</code></td><td>intocable</td><td>ninguna — solo necesitaba un reinicio</td><td>Sí — perdió su conexión a Redis después de que los <em>otros</em> dos contenedores fueron recreados</td></tr></tbody></table>\n<p>Dos soluciones de una línea. El costo real no fue la solución — fue notar que el tercer contenedor que nadie tocó fue el que se rompió.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"solución-de-problemas\">Solución de problemas<a href=\"https://wec.wiline.com/docs/es/tutorials/harden-container-privilege-non-root/#soluci%C3%B3n-de-problemas\" class=\"hash-link\" aria-label=\"Enlace directo al Solución de problemas\" title=\"Enlace directo al Solución de problemas\" translate=\"no\">​</a></h2>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"docker-compose-restart-dijo-no-se-proporcionó-archivo-de-configuración-no-encontrado\"><code>docker compose restart</code> dijo \"no se proporcionó archivo de configuración: no encontrado\"<a href=\"https://wec.wiline.com/docs/es/tutorials/harden-container-privilege-non-root/#docker-compose-restart-dijo-no-se-proporcion%C3%B3-archivo-de-configuraci%C3%B3n-no-encontrado\" class=\"hash-link\" aria-label=\"Enlace directo al docker-compose-restart-dijo-no-se-proporcionó-archivo-de-configuración-no-encontrado\" title=\"Enlace directo al docker-compose-restart-dijo-no-se-proporcionó-archivo-de-configuración-no-encontrado\" translate=\"no\">​</a></h3>\n<p>No estás en el directorio del proyecto que Compose espera — solo encuentra <code>docker-compose.yml</code> relativo a tu directorio de trabajo actual (o a través de <code>-f</code>). Primero, <code>cd</code> en la carpeta de la pila; este error exacto significa que Compose nunca miró siquiera a tus contenedores.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"el-trabajador-sigue-dando-errores-después-de-reiniciar-redis\">El trabajador sigue dando errores después de reiniciar Redis<a href=\"https://wec.wiline.com/docs/es/tutorials/harden-container-privilege-non-root/#el-trabajador-sigue-dando-errores-despu%C3%A9s-de-reiniciar-redis\" class=\"hash-link\" aria-label=\"Enlace directo al El trabajador sigue dando errores después de reiniciar Redis\" title=\"Enlace directo al El trabajador sigue dando errores después de reiniciar Redis\" translate=\"no\">​</a></h3>\n<p>Reiniciar la <em>dependencia</em> no ayuda — la conexión que se rompió pertenece al servicio <em>dependiente</em>. Reinicia la cosa que mantiene la conexión (aquí, <code>langfuse-worker</code>), no la cosa a la que se conecta.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"docker-exec-container-id-username-devuelve-no-existe-tal-usuario\"><code>docker exec &lt;container&gt; id &lt;username&gt;</code> devuelve \"no existe tal usuario\"<a href=\"https://wec.wiline.com/docs/es/tutorials/harden-container-privilege-non-root/#docker-exec-container-id-username-devuelve-no-existe-tal-usuario\" class=\"hash-link\" aria-label=\"Enlace directo al docker-exec-container-id-username-devuelve-no-existe-tal-usuario\" title=\"Enlace directo al docker-exec-container-id-username-devuelve-no-existe-tal-usuario\" translate=\"no\">​</a></h3>\n<p>La imagen no incluye un usuario no root dedicado bajo ese nombre — consulta la documentación de la imagen, o <code>docker exec &lt;container&gt; cat /etc/passwd</code> para ver qué está realmente disponible antes de elegir un UID.</p>\n<div class=\"skillComplete\"><span class=\"skillComplete__medal\" aria-hidden=\"true\">🎯</span><div class=\"skillComplete__text\"><div class=\"skillComplete__title\">Finished this tutorial?</div><div class=\"skillComplete__sub\">Mark it complete to earn <strong>Run containers as non-root</strong> on your skill path.</div></div><button type=\"button\" class=\"skillComplete__btn\">Mark as complete ✓</button></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"qué-sigue\">¿Qué sigue?<a href=\"https://wec.wiline.com/docs/es/tutorials/harden-container-privilege-non-root/#qu%C3%A9-sigue\" class=\"hash-link\" aria-label=\"Enlace directo al ¿Qué sigue?\" title=\"Enlace directo al ¿Qué sigue?\" translate=\"no\">​</a></h2>\n<p>El privilegio es la segunda capa: el endurecimiento de la red evita que un compromiso se propague entre pilas, el no root evita que un compromiso posea todo el contenedor. La última capa en esta serie es la identidad — poner autenticación real frente a los puertos que deben permanecer expuestos, que es donde <a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/\">NetBird</a> y Caddy entran en juego.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"lectura-adicional\">Lectura adicional<a href=\"https://wec.wiline.com/docs/es/tutorials/harden-container-privilege-non-root/#lectura-adicional\" class=\"hash-link\" aria-label=\"Enlace directo al Lectura adicional\" title=\"Enlace directo al Lectura adicional\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\"><a href=\"https://docs.docker.com/reference/dockerfile/#user\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Documentación de Docker — especificar un usuario para ejecutar el contenedor</a></li>\n<li class=\"\"><a href=\"https://hub.docker.com/_/postgres\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Imagen de Docker de PostgreSQL — ejecutándose como un usuario no root</a></li>\n<li class=\"\"><a href=\"https://github.com/redis/ioredis#connection-events\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">ioredis — comportamiento de conexión y reconexión</a></li>\n</ul>",
            "url": "https://wec.wiline.com/docs/es/tutorials/harden-container-privilege-non-root/",
            "title": "Root por defecto: endurecimiento del privilegio del contenedor en una pila de IA autoalojada",
            "summary": "Dos de los seis contenedores detrás de un despliegue en vivo de Langfuse estaban ejecutándose como root, sin razón aparente. Arreglarlo tomó una línea cada uno — y rompió un servicio que no tenía nada que ver con la solución. Una verdadera conversión, un verdadero fallo de coordinación, y cómo detectar ambos.",
            "date_modified": "2026-08-10T00:00:00.000Z",
            "author": {
                "name": "Rafael Fernandes",
                "url": "https://www.linkedin.com/in/rafaelmacariofernandes/"
            },
            "tags": [
                "docker",
                "seguridad",
                "contenedores",
                "autoalojamiento",
                "agentes",
                "endurecimiento"
            ]
        },
        {
            "id": "https://wec.wiline.com/docs/es/tutorials/harden-docker-networks-multi-agent/",
            "content_html": "<div class=\"tutorialHero\"><svg viewBox=\"0 0 24 24\" fill=\"#2496ED\" aria-label=\"Docker\" class=\"tutorialHero__docker\"><path d=\"M13.983 11.078h2.119a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.119a.185.185 0 00-.185.185v1.888c0 .102.083.185.185.185m-2.954-5.43h2.118a.186.186 0 00.186-.186V3.574a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m0 2.716h2.118a.187.187 0 00.186-.186V6.29a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.887c0 .102.082.185.185.186m-2.93 0h2.12a.186.186 0 00.184-.186V6.29a.185.185 0 00-.185-.185H8.1a.185.185 0 00-.185.185v1.887c0 .102.083.185.185.186m-2.964 0h2.119a.186.186 0 00.185-.186V6.29a.185.185 0 00-.185-.185H5.136a.186.186 0 00-.186.185v1.887c0 .102.084.185.186.186m5.893 2.715h2.118a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m-2.93 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.083.185.185.185m-2.964 0h2.119a.185.185 0 00.185-.185V9.006a.185.185 0 00-.184-.186h-2.12a.186.186 0 00-.186.186v1.887c0 .102.084.185.186.185m-2.92 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.082.185.185.185M23.763 9.89c-.065-.051-.672-.51-1.954-.51-.338.001-.676.03-1.01.087-.248-1.7-1.653-2.53-1.716-2.566l-.344-.199-.226.327c-.284.438-.49.922-.612 1.43-.23.97-.09 1.882.403 2.661-.595.332-1.55.413-1.744.42H.751a.751.751 0 00-.75.748 11.376 11.376 0 00.692 4.062c.545 1.428 1.355 2.48 2.41 3.124 1.18.723 3.1 1.137 5.275 1.137.983.003 1.963-.086 2.93-.266a12.248 12.248 0 003.823-1.389c.98-.567 1.86-1.288 2.61-2.136 1.252-1.418 1.998-2.997 2.553-4.4h.221c1.372 0 2.215-.549 2.68-1.009.309-.293.55-.65.707-1.046l.098-.288Z\"></path></svg><span class=\"tutorialHero__plus\">+</span><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"242\" height=\"255\" fill=\"none\" viewBox=\"0 0 242 255\" class=\"tutorialHero__wiline\"><path fill=\"#4E8EF7\" d=\"M56.711 11.293A195 195 0 0 1 41.899 4.79c4.788 1.85 9.71 4.028 14.812 6.504M73.807 29.35c-1.302-1.446-2.666-3.233-4.02-5.38 1.326 1.44 2.642 3.24 4.02 5.38M69.761 23.354c-.832-.585-1.752-1.472-2.558-2.714.9.567 1.685 1.49 2.558 2.714M62.97 15.774a10.7 10.7 0 0 1-3.158-1.841c.993.299 1.986.956 3.159 1.841M59.715 13.346c-.818-.194-1.782-.674-2.829-1.494.84.175 1.761.691 2.83 1.494M39.317 3.782c-.524.192-1.413.302-2.455.14.594-.255 1.342-.238 2.455-.14M74.906 31.339c-.334-.153-.73-.61-1.117-1.398.358.143.706.619 1.117 1.398M66.024 18.878c-.239.1-.557.05-.752-.276.35-.201.532-.069.752.276M71.773 150.625c3.363 7.291 7.185 14.411 9.983 21.914 4.474 11.997 11.167 20.832 24.296 24.757 10.62 3.175 20.369 1.795 30.093-1.816 9.937-3.689 15.165-11.978 18.501-21.541 4.349-12.466 9.385-24.443 19.668-33.446 11.32-9.909 24.639-14.788 39.429-15.63 8.3-.473 16.649-.081 25.444.444.472 15.673.385 30.825.55 45.974.038 3.499-1.049 4.719-4.557 4.543-4.982-.25-9.987-.079-14.98.009-.209.004-.4.991-.6 1.521-23.106 1.695-37.691 15.22-47.961 34.651-3.387 6.408-5.913 13.38-10.013 19.273-4.368 6.277-10.134 11.582-15.853 17.196-6.439 1.421-12.244 3.342-18.202 4.416-12.627 2.274-24.661.457-35.764-6.356-1.028-.631-2.395-.709-3.604-1.043 0 0-.117-.11-.047-.495-.818-.973-1.706-1.562-2.593-2.15 0 0 .09-.044.05-.335-.513-.545-.988-.798-1.463-1.052 0 0-.07-.123.012-.492-.561-.894-1.205-1.421-1.848-1.947 0 0 .134-.068.102-.28-.321-.442-.61-.671-.898-.901 0 0 .086-.007.074-.336-2.05-3.429-4.089-6.529-6.127-9.629 0 0 .094-.017.123-.365-3.013-6.117-6.056-11.886-9.1-17.655 0 0 .096-.01.088-.342-1.978-4.073-3.89-7.848-5.997-11.51-.246-.428-1.572-.235-2.398-.329 0 0-.226-.202-.114-.613-.213-.499-.537-.587-.861-.674 0 0-.032-.101.02-.455-.62-.846-1.291-1.339-1.964-1.832 0 0 .133-.083.119-.372-2.476-4.624-5.645-8.048-10.914-8.777 0 0 .108-.031.01-.326-1.673-1.444-3.105-2.918-4.85-3.688-9.751-4.308-20.013-5.643-30.63-5.23-7.305.285-7.316.051-7.317-7.446q-.002-18.496-.002-36.992v-3.57h36.106s-.08.019.133.283c1.43.415 2.646.567 3.863.719 0 0-.077.022.113.296 1.804.591 3.417.908 5.03 1.225 0 0 .139-.037.152.369 1.592.962 3.17 1.517 4.748 2.073 0 0-.091.027.052.267.735.415 1.328.59 1.92.765 0 0-.076.033.053.246.477.325.825.438 1.173.551 0 0-.149.018-.07.246.418.239.757.251 1.096.263 0 0 .23.058.189.465 1.522 1.319 3.084 2.231 4.647 3.143 0 0-.105.033-.015.316 2.723 2.764 5.356 5.244 7.989 7.724 0 0-.104.062-.094.352 1.01 1.401 2.01 2.512 3.01 3.624\"></path><path fill=\"#4E8EF7\" d=\"M214.77 52.819c-3.343.846-6.723 1.574-10.024 2.562-11.388 3.408-20.567 10.142-27.166 19.865-5.097 7.511-9.621 15.483-13.685 23.608-6.809 13.613-15.725 24.809-31.343 28.198-24.707 5.362-45.22-.126-57.852-24.564C69.466 92.363 63.8 82.375 57.349 73c-3.369-4.896-8.866-8.326-13.679-13.041-1.11-1.409-1.81-2.472-2.794-2.937-10.697-5.056-21.87-7.3-33.686-5.344-3.877.641-5.72-.297-5.615-4.86.294-12.816.26-25.646.019-38.464C1.509 3.874 3.15 2.5 7.429 2.716c9.774.492 19.564.678 29.347.989 0 0-.076.026.082.257 1.046.122 1.935.012 2.825-.098.575.043 1.15.086 2.038.692 5.265 2.72 10.218 4.877 15.17 7.034 0 0-.108.03-.016.317 1.058.765 2.022 1.245 2.987 1.724 0 0-.113.048-.082.354 1.144.876 2.258 1.446 3.372 2.017 0 0 .18.145.097.506.223.404.53.447.837.49 0 0 .214.13.144.504.244.426.558.477.873.528 0 0 .171.124.108.485.256.412.574.464.893.515 0 0 .167.127.105.493.265.437.592.509.919.58 0 0 .078.108.019.5.861 1.28 1.782 2.166 2.702 3.053 0 0-.101.061-.087.368 1.379 2.093 2.743 3.88 4.107 5.666 0 0-.094.011-.092.297.4.742.795 1.2 1.19 1.656 0 0-.121.02-.165.337.685 1.432 1.413 2.549 2.14 3.665 0 0-.11.024-.129.299.326.776.67 1.277 1.014 1.78 0 0-.084-.005-.1.367.933 4.079.311 8.497 4.648 10.67 4.69 12.164 12.107 21.794 25.852 23.497 19.618 2.431 35.321.062 45.299-24.554 3.398-8.383 7.099-16.902 12.232-24.264 8.353-11.979 21.046-17.795 35.832-20.32 1.666.417 2.649.651 3.624.622 9.93-.298 19.865-.513 29.785-1.032 3.818-.2 4.825 1.198 4.746 4.853-.218 9.967.004 19.944-.109 29.915-.052 4.583 1.149 10.606-1.239 13.302-2.044 2.308-8.394.86-12.842.967-3.82.092-7.644.036-11.467.045.065.31.097.633.213.922.034.084.306.072.469.105M37.585 127.354c-11.585.344-23.368.344-35.907.344v3.57q0 18.496.002 36.992c0 7.497.012 7.731 7.318 7.446 10.616-.413 20.878.922 30.63 5.23 1.743.77 3.176 2.244 4.84 3.744-13.655-7.873-28.93-7.767-44.468-7.398v-52.503c12.516.308 25.063-1.222 37.585 2.575\"></path><path fill=\"#4E8EF7\" d=\"M36.619 3.401c-9.626-.007-19.415-.193-29.19-.685C3.151 2.5 1.51 3.874 1.594 8.354c.241 12.818.275 25.648-.018 38.464-.105 4.563 1.737 5.501 5.614 4.86 11.816-1.956 22.99.288 33.686 5.344.984.465 1.685 1.529 2.616 2.66-13.39-7.538-28.284-7.343-43.479-6.78V0C12.314 1.045 24.388 2.071 36.62 3.401M215.232 52.852c-.625-.066-.897-.054-.931-.138-.116-.29-.148-.612-.212-.922 3.822-.01 7.646.047 11.467-.045 4.448-.108 10.797 1.341 12.842-.967 2.387-2.696 1.186-8.719 1.238-13.302.114-9.971-.109-19.948.109-29.915.08-3.655-.928-5.052-4.746-4.853-9.919.519-19.855.734-29.785 1.032-.975.03-1.958-.205-3.228-.58C214.559 1.945 227.423.991 240.73.003v52.882c-8.276 0-16.656 0-25.498-.033M220.071 177.398c-.271-.573-.08-1.561.129-1.565 4.993-.088 9.998-.259 14.98-.009 3.508.176 4.595-1.044 4.557-4.543-.165-15.149-.078-30.301-.088-45.911.458.372 1.32 1.202 1.324 2.036.079 16.424.059 32.848.059 50.036-6.984 0-13.737 0-20.961-.044M88.39 245.741c1.022.084 2.39.162 3.417.793 11.102 6.813 23.137 8.63 35.763 6.356 5.959-1.073 11.764-2.995 17.871-4.34-5.612 2.132-11.343 5.166-17.325 5.79-13.898 1.451-27.803 1.213-39.725-8.599M66.556 210.212c2.975 5.411 6.018 11.18 9.02 17.344-3.011-5.4-5.982-11.193-9.02-17.344M58.341 197.934c.666-.167 1.992-.36 2.238.068 2.107 3.662 4.018 7.437 5.985 11.557-2.69-3.535-5.376-7.449-8.223-11.625M44.637 185.241c5.099.438 8.268 3.862 10.737 8.564-3.518-2.513-7.043-5.393-10.737-8.564M82.29 48.422c-4.252-1.834-3.63-6.252-4.548-10.37 1.508 3.069 2.986 6.55 4.548 10.37M68.757 146.326c-2.533-2.157-5.166-4.637-7.882-7.455 2.539 2.151 5.16 4.641 7.882 7.455M75.532 228.226a122 122 0 0 1 6.047 9.319c-1.993-2.742-3.986-5.854-6.047-9.319M71.695 150.312c-.922-.799-1.921-1.91-2.906-3.366.952.788 1.89 1.921 2.906 3.366M46.663 130.007c-1.353-.103-2.966-.42-4.767-1.051 1.377.07 2.941.454 4.767 1.051M51.647 132.369a13.1 13.1 0 0 1-4.501-1.77c1.48.227 2.902.852 4.5 1.77M60.724 138.321a17.7 17.7 0 0 1-4.395-2.781c1.501.609 2.869 1.551 4.395 2.781M76.889 35.31c-.674-.781-1.402-1.898-2.068-3.39.711.768 1.362 1.912 2.068 3.39M85.668 243.124c.782.31 1.67.899 2.423 1.825-.862-.29-1.59-.919-2.423-1.825M41.596 128.372c-1.033.176-2.25.024-3.68-.432 1.023-.169 2.26-.032 3.68.432M55.367 194.37c.568.222 1.24.715 1.808 1.548-.637-.199-1.17-.738-1.808-1.548M82.397 239.295c.56.251 1.204.778 1.71 1.628-.633-.235-1.13-.794-1.71-1.628M84.291 241.679c.334.034.809.287 1.298.875-.376.004-.766-.326-1.298-.875M53.64 133.426c-.438.094-1.03-.081-1.764-.543.441-.101 1.026.086 1.764.543M77.805 37.409c-.322-.188-.666-.69-.978-1.52.34.182.648.693.978 1.52M81.588 238.108c.218-.039.507.19.823.707-.233.045-.493-.196-.823-.707M55.906 134.832c-.198.157-.537.145-.92-.161.262-.29.537-.195.92.161M57.276 196.552c.254-.079.578.009.756.361-.377.171-.557.018-.756-.361M54.888 134.25c-.215.129-.563.017-1.038-.351.217-.133.56-.012 1.038.351M64.014 16.858a.545.545 0 0 1-.71-.247c.344-.19.51-.067.71.247M65.027 17.882a.574.574 0 0 1-.734-.272c.35-.193.525-.063.734.272M67.045 19.939c-.244.093-.57.021-.777-.329.358-.192.55-.044.777.329\"></path></svg></div>\n<div class=\"skillTracker\" tabindex=\"0\" aria-label=\"Skill path: 0 of 3 skills earned\"><div class=\"skillTracker__ring\" style=\"--skillTracker-pct:0%\"><span class=\"skillTracker__ringLabel\">0<!-- -->/<!-- -->3</span></div><div class=\"skillTracker__panel\"><div class=\"skillTracker__header\"><span class=\"skillTracker__title\">🎯 Skill path</span><span class=\"skillTracker__count\">0<!-- -->/<!-- -->3<!-- --> earned</span></div><div class=\"skillTracker__series\">Hardening self-hosted AI infra</div><ul class=\"skillTracker__steps\"><li><span class=\"skillTracker__step\"><span class=\"skillTracker__dot\" data-state=\"current\">1</span><span class=\"skillTracker__skill\" data-state=\"current\">Lock down Docker networks</span></span></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/harden-container-privilege-non-root/\"><span class=\"skillTracker__dot\" data-state=\"locked\">2</span><span class=\"skillTracker__skill\" data-state=\"locked\">Run containers as non-root</span></a></li><li><span class=\"skillTracker__step\"><span class=\"skillTracker__dot\" data-state=\"locked\">🏆</span><span class=\"skillTracker__skill\" data-state=\"locked\">Identity in front of every port</span></span></li></ul><div class=\"skillTracker__footer\">Finish this tutorial, then mark it complete below to earn the skill.</div></div></div>\n<p>Comienzas con un agente. Luego necesita una base de datos. Después agregas un segundo agente, un puente de mensajería, una pila de observabilidad. Seis meses después, una única instancia de WEC está ejecutando cinco proyectos de compose, veinte y tantos contenedores, y nadie recuerda qué puertos están abiertos al mundo.</p>\n<p>Eso no es un hipotético — esa es la caja en la que se escribió este tutorial. Así que en lugar de teorizar, lo investigamos: ¿pueden los contenedores alcanzarse entre sí a través de pilas? ¿Pueden alcanzar las bases de datos? ¿Está el firewall realmente protegiendo algo?</p>\n<p>Tres de las respuestas me sorprendieron. Una de ellas fue una base de datos sentada allí sin contraseña. Y el firewall — el firewall estaba mintiendo.</p>\n<!-- -->\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"la-caja-que-estamos-auditando\">La caja que estamos auditando<a href=\"https://wec.wiline.com/docs/es/tutorials/harden-docker-networks-multi-agent/#la-caja-que-estamos-auditando\" class=\"hash-link\" aria-label=\"Enlace directo al La caja que estamos auditando\" title=\"Enlace directo al La caja que estamos auditando\" translate=\"no\">​</a></h2>\n<p>Cinco proyectos de compose independientes, desplegados durante meses, cada uno con su propia red (<code>host</code>, <code>none</code> y <code>bridge</code> son los integrados de Docker, más uno sobrante de un servicio que no está en ejecución):</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> network </span><span class=\"token function\" style=\"color:#d73a49\">ls</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">NETWORK ID     NAME                             DRIVER    SCOPE</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">655850b24125   bridge                           bridge    local</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">05e5d49e7e9d   evolution-api_default            bridge    local</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">e8bd75ecbb0a   host                             host      local</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">ce697baac173   langfuse_default                 bridge    local</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">ead3e0822ce6   nlp-evaluation-service_nlp-net   bridge    local</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">5d0ab32d14d7   none                             null      local</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">9a6727150f2b   openclaw_default                 bridge    local</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">ec77b98f457b   rag-service_default              bridge    local</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">5f88cabf97f0   remark42_default                 bridge    local</span><br></div></code></pre></div></div>\n<p>Y aquí está lo que se publica al mundo:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">ps</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--format</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'{{.Names}}\\t{{.Ports}}'</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">grep</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'0.0.0.0'</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">remark42-remark42-1             0.0.0.0:8082-&gt;8080/tcp, [::]:8082-&gt;8080/tcp</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">evolution-api-evolution-api-1   0.0.0.0:8080-&gt;8080/tcp, [::]:8080-&gt;8080/tcp</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">langfuse-langfuse-web-1         0.0.0.0:3001-&gt;3000/tcp, [::]:3001-&gt;3000/tcp</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">langfuse-minio-1                0.0.0.0:9090-&gt;9000/tcp, [::]:9090-&gt;9000/tcp, 127.0.0.1:9091-&gt;9001/tcp</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"La auditoría de exposición — nueve redes listadas, cuatro servicios vinculados a cada interfaz\" src=\"https://wec.wiline.com/docs/es/assets/images/netsec-exposure-audit-904ef03d2c7552d9b1b8c9d9e69218dd.png\" width=\"1045\" height=\"300\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 1.</strong> El inicio de cualquier auditoría. Nueve redes: tres integradas de Docker (<code>bridge</code>, <code>host</code>, <code>none</code>), cinco proyectos de compose, y uno sobrante. Cuatro servicios publicados en <code>0.0.0.0</code> — cada interfaz, incluida la pública.</p>\n<p>Cuatro servicios escuchando en cada interfaz. Nota lo que <em>no</em> está en esa lista: los Postgres, ClickHouse y Redis detrás de Langfuse. Esos fueron publicados en <code>127.0.0.1</code> en su lugar — alguien tomó una buena decisión allí. Mantén ese pensamiento.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"prueba-1--aislan-realmente-las-redes-docker-separadas-sí-y-la-mayoría-de-los-blogs-están-equivocados\">Prueba 1 — ¿Aislan realmente las redes Docker separadas? (sí, y la mayoría de los blogs están equivocados)<a href=\"https://wec.wiline.com/docs/es/tutorials/harden-docker-networks-multi-agent/#prueba-1--aislan-realmente-las-redes-docker-separadas-s%C3%AD-y-la-mayor%C3%ADa-de-los-blogs-est%C3%A1n-equivocados\" class=\"hash-link\" aria-label=\"Enlace directo al Prueba 1 — ¿Aislan realmente las redes Docker separadas? (sí, y la mayoría de los blogs están equivocados)\" title=\"Enlace directo al Prueba 1 — ¿Aislan realmente las redes Docker separadas? (sí, y la mayoría de los blogs están equivocados)\" translate=\"no\">​</a></h2>\n<p>La preocupación clásica: OpenClaw es comprometido, y el atacante entra en la base de datos de Langfuse al lado. Vamos a averiguarlo en lugar de adivinar. Obtén las IPs de los contenedores:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token keyword\" style=\"color:#00009f\">for</span><span class=\"token plain\"> </span><span class=\"token for-or-select variable\" style=\"color:#36acaa\">c</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:#00009f\">in</span><span class=\"token plain\"> langfuse-postgres-1 evolution-api-evolution-postgres-1</span><span class=\"token punctuation\" style=\"color:#393A34\">;</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:#00009f\">do</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> inspect </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-f</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'{{.Name}} {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'</span><span class=\"token plain\"> </span><span class=\"token variable\" style=\"color:#36acaa\">$c</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">done</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">/langfuse-postgres-1 172.19.0.5</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">/evolution-api-evolution-postgres-1 172.23.0.4</span><br></div></code></pre></div></div>\n<p>Ahora intenta alcanzar ambos <strong>desde dentro del contenedor de OpenClaw</strong>, por IP — sin DNS, sin atajos:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">exec</span><span class=\"token plain\"> openclaw-openclaw-gateway-1 </span><span class=\"token function\" style=\"color:#d73a49\">node</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-e</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">const net=require(\"net\");</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">[[\"langfuse-postgres\",\"172.19.0.5\",5432],[\"evolution-postgres\",\"172.23.0.4\",5432]]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  .forEach(([n,ip,p])=&gt;{</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    const s=net.connect({host:ip,port:p,timeout:4000});</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    s.on(\"connect\",()=&gt;{console.log(`ALCANZADO   ${n}`);s.destroy()});</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    s.on(\"timeout\",()=&gt;{console.log(`bloqueado   ${n}`);s.destroy()});</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    s.on(\"error\",e=&gt;console.log(`error     ${n} ${e.code}`));</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  });'</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">bloqueado   langfuse-postgres</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">bloqueado   evolution-postgres</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Las sondas entre redes se agotan desde dentro del contenedor de OpenClaw\" src=\"https://wec.wiline.com/docs/es/assets/images/netsec-isolation-blocked-52d0644c0dbd63427ca62b09eb5b9947.png\" width=\"759\" height=\"182\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 2.</strong> Conectando por IP cruda desde el contenedor de una pila a la base de datos de otra pila: ambos se agotan. Las reglas de aislamiento de Docker están haciendo su trabajo.</p>\n<p><strong>Bloqueado.</strong> Las instalaciones modernas de Docker instalan reglas de <code>DOCKER-ISOLATION-STAGE</code> que eliminan el tráfico entre redes de puente definidas por el usuario, y funcionan. Si has leído que \"las redes Docker no realmente aíslan\", pruébalo en tu propia caja antes de creerlo — en Docker actual, sí lo hacen.</p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Habilidad desbloqueada 🏅</div><div class=\"admonitionContent_BuS1\"><p>Puedes verificar el aislamiento de red empíricamente en lugar de confiar en el folclore: obtén la IP del objetivo con <code>docker inspect</code>, luego conecta <strong>por IP</strong> desde dentro de otro contenedor.</p></div></div>\n<p>Así que el aislamiento entre pilas está bien. Lo que significa que el verdadero riesgo está en otro lugar.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"prueba-2--dentro-de-una-red-todo-está-completamente-abierto\">Prueba 2 — Dentro de una red, todo está completamente abierto<a href=\"https://wec.wiline.com/docs/es/tutorials/harden-docker-networks-multi-agent/#prueba-2--dentro-de-una-red-todo-est%C3%A1-completamente-abierto\" class=\"hash-link\" aria-label=\"Enlace directo al Prueba 2 — Dentro de una red, todo está completamente abierto\" title=\"Enlace directo al Prueba 2 — Dentro de una red, todo está completamente abierto\" translate=\"no\">​</a></h2>\n<p>Cada proyecto de compose coloca todos sus servicios en una red. Esa es la predeterminada, y significa que el contenedor de la aplicación puede alcanzar cada servicio de respaldo — lo cual es <em>intencionado</em>. La pregunta es cómo se ve realmente ese acceso. Desde el contenedor de puente de Evolution API:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">exec</span><span class=\"token plain\"> evolution-api-bridge-1 python3 </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">import socket</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">for name, host, port in [(\"postgres\",\"evolution-postgres\",5432),</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">                         (\"redis\",\"evolution-redis\",6379),</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">                         (\"api\",\"evolution-api\",8080)]:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    s=socket.socket(); s.settimeout(4)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    try:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        s.connect((host,port)); print(f\"ALCANZADO   {name}:{port}\")</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    except Exception as e:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        print(f\"bloqueado   {name}:{port} -- {type(e).__name__}\")</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    finally: s.close()'</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">ALCANZADO   postgres:5432</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">ALCANZADO   redis:6379</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">ALCANZADO   api:8080</span><br></div></code></pre></div></div>\n<p>Alcanzable es esperado. <strong>No autenticado no lo es.</strong> Verifica si esos servicios realmente piden credenciales — primero Redis:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">exec</span><span class=\"token plain\"> evolution-api-bridge-1 python3 </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">import socket</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">s=socket.socket(); s.settimeout(5); s.connect((\"evolution-redis\",6379))</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">s.sendall(b\"INFO server\\r\\n\")</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">print(s.recv(200).decode(errors=\"replace\")[:120])'</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">$625</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"># Server</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">redis_version:7.4.9</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">redis_git_sha1:00000000</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">redis_git_dirty:0</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">redis_build_id:b61b4eb609520881</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">redis_</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Redis respondiendo INFO sin AUTH\" src=\"https://wec.wiline.com/docs/es/assets/images/netsec-redis-noauth-22e25e2b5bbf007d514a914ec9ec8b91.png\" width=\"744\" height=\"226\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 3.</strong> El hallazgo: <code>INFO server</code> respondió sin credenciales (salida truncada a 120 caracteres por la sonda). Cualquier contenedor en esta red puede leer o borrar el almacén de sesiones.</p>\n<p>Eso es un <strong>comando <code>INFO</code> respondido sin <code>AUTH</code></strong> — el Redis que mantiene el estado de sesión de este despliegue no tiene contraseña. Cualquier cosa que obtenga ejecución de código en <em>cualquier</em> contenedor en esa red puede leerlo, escribir en él, o <code>FLUSHALL</code> en él.</p>\n<p>Postgres, en la misma pila, se comporta correctamente:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">exec</span><span class=\"token plain\"> evolution-api-bridge-1 python3 </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">import socket,struct</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">s=socket.socket(); s.settimeout(5); s.connect((\"evolution-postgres\",5432))</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">msg=b\"user\\x00postgres\\x00database\\x00postgres\\x00\\x00\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">s.sendall(struct.pack(\"!ii\",len(msg)+8,196608)+msg)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">print(s.recv(64))'</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">b'R\\x00\\x00\\x00\\x17\\x00\\x00\\x00\\nSCRAM-SHA-256\\x00\\x00'</span><br></div></code></pre></div></div>\n<p>Exige SCRAM-SHA-256. La misma caja, la misma red, dos servicios de respaldo — uno pide una contraseña, el otro no. Nadie decidió eso; es simplemente lo que las imágenes predeterminan cuando no estableces una contraseña.</p>\n<p><strong>La lección: \"red interna\" no es un límite de seguridad.</strong> Es una conveniencia. Trata cada servicio como si el atacante ya estuviera en esa red, porque si un contenedor cae, ellos están.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"prueba-3--el-firewall-que-no-es\">Prueba 3 — El firewall que no es<a href=\"https://wec.wiline.com/docs/es/tutorials/harden-docker-networks-multi-agent/#prueba-3--el-firewall-que-no-es\" class=\"hash-link\" aria-label=\"Enlace directo al Prueba 3 — El firewall que no es\" title=\"Enlace directo al Prueba 3 — El firewall que no es\" translate=\"no\">​</a></h2>\n<p>Ahora lo grande. Activa UFW y deniega todo lo entrante excepto SSH:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">sudo</span><span class=\"token plain\"> ufw allow </span><span class=\"token number\" style=\"color:#36acaa\">22</span><span class=\"token plain\">/tcp          </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># SIEMPRE primero, o te bloqueas</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">sudo</span><span class=\"token plain\"> ufw default deny incoming</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">sudo</span><span class=\"token plain\"> ufw </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--force</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">enable</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">sudo</span><span class=\"token plain\"> ufw status verbose</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Status: active</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Logging: on (low)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Default: deny (incoming), allow (outgoing), deny (routed)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">New profiles: skip</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">To                         Action      From</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">--                         ------      ----</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">60000:61000/udp            ALLOW IN    Anywhere</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">22/tcp                     ALLOW IN    Anywhere</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">60000:61000/udp (v6)       ALLOW IN    Anywhere (v6)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">22/tcp (v6)                ALLOW IN    Anywhere (v6)</span><br></div></code></pre></div></div>\n<p>Denegar entrante — solo SSH y el rango UDP de mosh permitido. Ahora, desde una <strong>máquina diferente</strong>, abre la interfaz de usuario de Langfuse y la API de Evolution:</p>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Desde una laptop, no el servidor</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">http://&lt;instance-ip&gt;:3001   -&gt;  La interfaz de usuario de Langfuse se carga normalmente</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">http://&lt;instance-ip&gt;:8080   -&gt;  {\"status\":200,\"message\":\"Bienvenido a la API de Evolution...\"}</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"La API de Evolution respondiendo en un navegador mientras UFW deniega todo el tráfico entrante\" src=\"https://wec.wiline.com/docs/es/assets/images/netsec-ufw-bypass-130569873d3680226c3534f016c4756a.png\" width=\"474\" height=\"239\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<p><strong>Figura 4.</strong> El firewall dice que deniega todo lo entrante. La API de Evolution en <code>:8080</code> responde de todos modos — estado 200, directamente desde un navegador en otra máquina.</p>\n<p>Ambos completamente abiertos, a través de un firewall configurado para bloquearlos. Esta es la sorpresa de seguridad más común en Docker auto-alojado, y no es un error.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"por-qué-docker-llega-al-paquete-primero\">Por qué: Docker llega al paquete primero<a href=\"https://wec.wiline.com/docs/es/tutorials/harden-docker-networks-multi-agent/#por-qu%C3%A9-docker-llega-al-paquete-primero\" class=\"hash-link\" aria-label=\"Enlace directo al Por qué: Docker llega al paquete primero\" title=\"Enlace directo al Por qué: Docker llega al paquete primero\" translate=\"no\">​</a></h3>\n<p>Mira el orden de la cadena <code>FORWARD</code>:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">sudo</span><span class=\"token plain\"> iptables </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-L</span><span class=\"token plain\"> FORWARD </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-n</span><span class=\"token plain\"> --line-numbers </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">head</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-8</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Chain FORWARD (policy DROP)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">num  target     prot opt source               destination</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">1    DOCKER-USER  all  --  0.0.0.0/0            0.0.0.0/0</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">2    DOCKER-FORWARD  all  --  0.0.0.0/0            0.0.0.0/0</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">3    ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0            ctstate RELATED,ESTABLISHED</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">4    ACCEPT     all  --  0.0.0.0/0            0.0.0.0/0</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">5    ufw-before-logging-forward  all  --  0.0.0.0/0            0.0.0.0/0</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">6    ufw-before-forward  all  --  0.0.0.0/0            0.0.0.0/0</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"UFW activo con denegar entrante, y las cadenas de Docker delante de las de UFW en FORWARD\" src=\"https://wec.wiline.com/docs/es/assets/images/netsec-ufw-chains-7a5553d8ce1882eb415c7e12e1beb065.png\" width=\"1031\" height=\"395\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 5.</strong> La explicación completa en una pantalla: el firewall dice <em>denegar entrante</em>, y las cadenas de Docker están en las posiciones 1-2 mientras que las de UFW comienzan en 5.</p>\n<p>Las cadenas de Docker están en las posiciones <strong>1 y 2</strong>. Las de UFW no aparecen hasta <strong>5 y 6</strong>. Y publicar un puerto no es un oyente en el host — es una regla de NAT:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">sudo</span><span class=\"token plain\"> iptables </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-t</span><span class=\"token plain\"> nat </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-L</span><span class=\"token plain\"> DOCKER </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-n</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">grep</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-E</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'3001|8080'</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">DNAT tcp -- 0.0.0.0/0  0.0.0.0/0  tcp dpt:8080 to:172.23.0.3:8080</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">DNAT tcp -- 0.0.0.0/0  0.0.0.0/0  tcp dpt:3001 to:172.19.0.2:3000</span><br></div></code></pre></div></div>\n<p>El paquete llega, el NAT de Docker reescribe el destino al contenedor, y se convierte en tráfico <em>reenviado</em> que la propia cadena de Docker acepta — mucho antes de que se consulte cualquier regla de UFW. Tus reglas de UFW no son ignoradas; simplemente nunca se alcanzan.</p>\n<div class=\"theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z\"></path></svg></span>Esto es lo que debes recordar</div><div class=\"admonitionContent_BuS1\"><p><code>ufw deny</code> <strong>no</strong> protege un puerto Docker publicado. Si has estado confiando en UFW frente a <code>-p 8080:8080</code>, ese puerto ha estado abierto todo el tiempo.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"cuatro-soluciones-en-orden-de-preferencia\">Cuatro soluciones, en orden de preferencia<a href=\"https://wec.wiline.com/docs/es/tutorials/harden-docker-networks-multi-agent/#cuatro-soluciones-en-orden-de-preferencia\" class=\"hash-link\" aria-label=\"Enlace directo al Cuatro soluciones, en orden de preferencia\" title=\"Enlace directo al Cuatro soluciones, en orden de preferencia\" translate=\"no\">​</a></h2>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"solución-1--no-lo-publiques-en-absoluto\">Solución 1 — No lo publiques en absoluto<a href=\"https://wec.wiline.com/docs/es/tutorials/harden-docker-networks-multi-agent/#soluci%C3%B3n-1--no-lo-publiques-en-absoluto\" class=\"hash-link\" aria-label=\"Enlace directo al Solución 1 — No lo publiques en absoluto\" title=\"Enlace directo al Solución 1 — No lo publiques en absoluto\" translate=\"no\">​</a></h3>\n<p>La solución más limpia no es una regla de firewall; es no abrir la puerta. Si un servicio solo es consumido por otros contenedores, no necesita <strong>ninguna</strong> entrada <code>ports:</code> — el tráfico de servicio a servicio funciona a través de la red de compose por nombre.</p>\n<p>Si necesitas que sea accesible desde el host (un puerto de depuración, un psql local), enlázalo explícitamente al loopback:</p>\n<div class=\"language-yaml codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-yaml codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token key atrule\" style=\"color:#00a4db\">services</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">postgres</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># prefijo 127.0.0.1 = solo host, nunca la interfaz pública</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">ports</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"127.0.0.1:5433:5432\"</span><br></div></code></pre></div></div>\n<p>Eso es exactamente lo que la pila de Langfuse en esta caja ya hace — su Postgres, ClickHouse y Redis están todos en <code>127.0.0.1</code>, que es por qué nunca aparecieron en nuestra auditoría de exposición. El predeterminado <code>\"5433:5432\"</code> significa <code>0.0.0.0</code> — cada interfaz, incluida la pública.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"solución-2--divide-las-redes-por-zona-de-confianza-y-marca-los-backends-como-internal\">Solución 2 — Divide las redes por zona de confianza y marca los backends como <code>internal</code><a href=\"https://wec.wiline.com/docs/es/tutorials/harden-docker-networks-multi-agent/#soluci%C3%B3n-2--divide-las-redes-por-zona-de-confianza-y-marca-los-backends-como-internal\" class=\"hash-link\" aria-label=\"Enlace directo al solución-2--divide-las-redes-por-zona-de-confianza-y-marca-los-backends-como-internal\" title=\"Enlace directo al solución-2--divide-las-redes-por-zona-de-confianza-y-marca-los-backends-como-internal\" translate=\"no\">​</a></h3>\n<p>Una red por proyecto de compose es la predeterminada, no un diseño. Da a los servicios de respaldo su propia red, y coloca solo la aplicación en ambas:</p>\n<div class=\"language-yaml codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">docker-compose.yml</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-yaml codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token key atrule\" style=\"color:#00a4db\">services</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">app</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\">                      </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># habla con el mundo Y con la base de datos</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">image</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> your</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">agent</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">networks</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token plain\">frontend</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> backend</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">db</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">image</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> redis</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token number\" style=\"color:#36acaa\">7</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">command</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> redis</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">server </span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">requirepass $</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\">REDIS_PASSWORD</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">networks</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token plain\">backend</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\">     </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># solo backend — sin ruta hacia afuera</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">public</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\">                   </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># por ejemplo, un receptor de webhook</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">image</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> your</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">public</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">thing</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">networks</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token plain\">frontend</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># no puede ver la base de datos en absoluto</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token key atrule\" style=\"color:#00a4db\">networks</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">frontend</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">backend</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">internal</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token boolean important\" style=\"color:#36acaa\">true</span><span class=\"token plain\">          </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># sin gateway: sin entrada, sin salida</span><br></div></code></pre></div></div>\n<p>Verificado en la misma caja:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># el contenedor solo de frontend intenta alcanzar la base de datos</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">exec</span><span class=\"token plain\"> netsecdemo-public-1 python3 </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">import socket</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">s=socket.socket(); s.settimeout(4)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">try: s.connect((\"db\",6379)); print(\"ALCANZADO db  &lt;- fuga\")</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">except Exception as e: print(f\"bloqueado: {type(e).__name__}\")'</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># la app, que está en ambas redes</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">exec</span><span class=\"token plain\"> netsecdemo-app-1 python3 </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">import socket</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">s=socket.socket(); s.settimeout(4)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">try: s.connect((\"db\",6379)); print(\"ALCANZADO db  &lt;- correcto, lo necesita\")</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">except Exception as e: print(f\"bloqueado: {type(e).__name__}\")'</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># y puede la base de datos alcanzar Internet?</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">exec</span><span class=\"token plain\"> netsecdemo-db-1 </span><span class=\"token function\" style=\"color:#d73a49\">timeout</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">6</span><span class=\"token plain\"> getent hosts pypi.org </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\">/dev/null </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token operator\" style=\"color:#393A34\">&amp;&amp;</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">echo</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"resuelto DNS externo\"</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">||</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">echo</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"sin salida a Internet  &lt;- interno funcionando\"</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">bloqueado: gaierror</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">ALCANZADO db  &lt;- correcto, lo necesita</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">sin salida a Internet  &lt;- interno funcionando</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"La demostración de red interna: público bloqueado, app permitida, base de datos sin salida\" src=\"https://wec.wiline.com/docs/es/assets/images/netsec-internal-demo-6231d6936686cf9d282c7f494797a185.png\" width=\"371\" height=\"163\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<p><strong>Figura 6.</strong> Tres propiedades probadas en una ejecución — el contenedor de cara pública ni siquiera puede resolver la base de datos, la app puede alcanzarla, y la base de datos no tiene ruta a Internet.</p>\n<p>Tres propiedades a la vez: el contenedor de cara pública ni siquiera puede resolver la base de datos, la app puede, y la base de datos no puede comunicarse — lo cual importa el día que una dependencia tuya se vuelva maliciosa.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"solución-3--autentica-todo-incluidos-los-servicios-internos\">Solución 3 — Autentica todo, incluidos los servicios \"internos\"<a href=\"https://wec.wiline.com/docs/es/tutorials/harden-docker-networks-multi-agent/#soluci%C3%B3n-3--autentica-todo-incluidos-los-servicios-internos\" class=\"hash-link\" aria-label=\"Enlace directo al Solución 3 — Autentica todo, incluidos los servicios &quot;internos&quot;\" title=\"Enlace directo al Solución 3 — Autentica todo, incluidos los servicios &quot;internos&quot;\" translate=\"no\">​</a></h3>\n<p>Nuestra auditoría encontró a Redis respondiendo <code>INFO</code> sin credenciales. Una línea lo soluciona:</p>\n<div class=\"language-yaml codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-yaml codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">db</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">image</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> redis</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token number\" style=\"color:#36acaa\">7</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">command</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> redis</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">server </span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">requirepass $</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\">REDIS_PASSWORD</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida (después)</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">-NOAUTH Authentication required.</span><br></div></code></pre></div></div>\n<p>Esa es la salida real de esta caja, después de aplicar la solución — el hallazgo de la Prueba 2 está cerrado. Si estableces una contraseña en un Redis que algo ya usa, recuerda actualizar la cadena de conexión del cliente también (<code>redis://:&lt;password&gt;@host:6379/0</code>), o cambiarás una base de datos abierta por una rota.</p>\n<p>Haz esto incluso para servicios que no están publicados. La defensa en profundidad significa que la segunda capa se mantiene cuando la primera falla.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"solución-4--cuando-debes-publicar-filtra-en-docker-user\">Solución 4 — Cuando debes publicar, filtra en <code>DOCKER-USER</code><a href=\"https://wec.wiline.com/docs/es/tutorials/harden-docker-networks-multi-agent/#soluci%C3%B3n-4--cuando-debes-publicar-filtra-en-docker-user\" class=\"hash-link\" aria-label=\"Enlace directo al solución-4--cuando-debes-publicar-filtra-en-docker-user\" title=\"Enlace directo al solución-4--cuando-debes-publicar-filtra-en-docker-user\" translate=\"no\">​</a></h3>\n<p>A veces un puerto realmente tiene que ser de cara pública pero restringido por origen. La cadena a usar es <code>DOCKER-USER</code> — Docker garantiza que se ejecute <strong>antes</strong> de sus propias reglas de aceptación, y nunca la sobrescribe:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># solo 203.0.113.0/24 puede abrir nuevas conexiones al puerto 3000 del contenedor</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">sudo</span><span class=\"token plain\"> iptables </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-I</span><span class=\"token plain\"> DOCKER-</span><span class=\"token environment constant\" style=\"color:#36acaa\">USER</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">1</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-p</span><span class=\"token plain\"> tcp </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--dport</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">3000</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-m</span><span class=\"token plain\"> conntrack </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--ctstate</span><span class=\"token plain\"> NEW </span><span class=\"token operator\" style=\"color:#393A34\">!</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-s</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">203.0</span><span class=\"token plain\">.113.0/24 </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-j</span><span class=\"token plain\"> DROP</span><br></div></code></pre></div></div>\n<p>Probado en vivo en el puerto de Langfuse, desde un navegador en otra máquina:</p>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Antes de la regla</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">http://&lt;instance-ip&gt;:3001  -&gt;  La interfaz de usuario de Langfuse se carga</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Después de la regla</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">ERR_CONNECTION_TIMED_OUT</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"La misma URL agotándose después de la regla DOCKER-USER\" src=\"https://wec.wiline.com/docs/es/assets/images/netsec-docker-user-blocked-8e34c54fd3b4552806e49bf87aed7cfb.png\" width=\"2524\" height=\"1288\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<p><strong>Figura 7.</strong> Misma URL, mismo firewall, una regla en la cadena correcta — y el puerto está finalmente cerrado. Nota la propia sugerencia de Chrome: <em>\"Verificando el proxy y el firewall.\"</em> Esta vez el firewall realmente es la respuesta.</p>\n<p>Mientras tanto, la API de Evolution en <code>:8080</code>, que deliberadamente dejamos sola, siguió respondiendo. La regla es quirúrgica, y funciona exactamente donde UFW no pudo.</p>\n<div class=\"theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z\"></path></svg></span>Haz que sobreviva a un reinicio</div><div class=\"admonitionContent_BuS1\"><p><code>iptables -I</code> no es persistente. Usa <code>iptables-persistent</code> (<code>netfilter-persistent save</code>), o coloca la regla en una pequeña unidad de systemd que se ejecute después de <code>docker.service</code>. Una regla de firewall que desaparece al reiniciar es peor que ninguna, porque pensarás que estás protegido.</p></div></div>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Habilidad desbloqueada 🏅</div><div class=\"admonitionContent_BuS1\"><p>Puedes auditar y asegurar un host Docker multi-pila: prueba qué es alcanzable, enlaza o divide lo que no debería ser, autentica los backends, y filtra los puertos publicados en la única cadena que Docker respeta.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"la-auditoría-como-una-lista-de-verificación\">La auditoría, como una lista de verificación<a href=\"https://wec.wiline.com/docs/es/tutorials/harden-docker-networks-multi-agent/#la-auditor%C3%ADa-como-una-lista-de-verificaci%C3%B3n\" class=\"hash-link\" aria-label=\"Enlace directo al La auditoría, como una lista de verificación\" title=\"Enlace directo al La auditoría, como una lista de verificación\" translate=\"no\">​</a></h2>\n<p>Ejecuta estos cuatro comandos en cualquier caja que poseas. Toman un minuto y te dicen más que cualquier diagrama de arquitectura:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># 1. ¿Qué está expuesto al mundo?</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">ps</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--format</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'{{.Names}}\\t{{.Ports}}'</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">grep</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'0.0.0.0'</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># 2. ¿Están tus reglas de firewall incluso en la ruta? (Las cadenas de Docker antes de ufw = no lo están)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">sudo</span><span class=\"token plain\"> iptables </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-L</span><span class=\"token plain\"> FORWARD </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-n</span><span class=\"token plain\"> --line-numbers </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">head</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-8</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># 3. ¿Qué puede alcanzar un contenedor comprometido en su propia red?</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">exec</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&lt;</span><span class=\"token plain\">container</span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> python3 </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"import socket;s=socket.socket();s.settimeout(3);s.connect</span><span class=\"token string variable punctuation\" style=\"color:#393A34\">((</span><span class=\"token string variable\" style=\"color:#36acaa\">'</span><span class=\"token string variable operator\" style=\"color:#393A34\">&lt;</span><span class=\"token string variable\" style=\"color:#36acaa\">svc</span><span class=\"token string variable operator\" style=\"color:#393A34\">&gt;</span><span class=\"token string variable\" style=\"color:#36acaa\">'</span><span class=\"token string variable punctuation\" style=\"color:#393A34\">,</span><span class=\"token string variable operator\" style=\"color:#393A34\">&lt;</span><span class=\"token string variable\" style=\"color:#36acaa\">port</span><span class=\"token string variable operator\" style=\"color:#393A34\">&gt;</span><span class=\"token string variable punctuation\" style=\"color:#393A34\">))</span><span class=\"token string\" style=\"color:#e3116c\">;print('alcanzado')\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># 4. ¿Ese servicio de respaldo pide una contraseña?</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">exec</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&lt;</span><span class=\"token plain\">container</span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> python3 </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">import socket;s=socket.socket();s.settimeout(4);s.connect</span><span class=\"token string variable punctuation\" style=\"color:#393A34\">((</span><span class=\"token string variable\" style=\"color:#36acaa\">'redis'</span><span class=\"token string variable punctuation\" style=\"color:#393A34\">,</span><span class=\"token string variable number\" style=\"color:#36acaa\">6379</span><span class=\"token string variable punctuation\" style=\"color:#393A34\">))</span><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">s.sendall(b'INFO server</span><span class=\"token string entity\" style=\"color:#36acaa\">\\r</span><span class=\"token string entity\" style=\"color:#36acaa\">\\n</span><span class=\"token string\" style=\"color:#e3116c\">');print(s.recv(60))\"</span><br></div></code></pre></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"lo-que-encontramos-y-lo-que-costó-arreglar\">Lo que encontramos, y lo que costó arreglar<a href=\"https://wec.wiline.com/docs/es/tutorials/harden-docker-networks-multi-agent/#lo-que-encontramos-y-lo-que-cost%C3%B3-arreglar\" class=\"hash-link\" aria-label=\"Enlace directo al Lo que encontramos, y lo que costó arreglar\" title=\"Enlace directo al Lo que encontramos, y lo que costó arreglar\" translate=\"no\">​</a></h2>\n<table><thead><tr><th>Hallazgo</th><th>Severidad</th><th>Solución</th></tr></thead><tbody><tr><td>El aislamiento entre redes funciona</td><td>✅ ninguno</td><td>nada — Docker ya hace esto</td></tr><tr><td>Cada servicio en una red, mutuamente alcanzable</td><td>⚠️ medio</td><td>dividir por zona de confianza, <code>internal: true</code></td></tr><tr><td>Redis respondiendo sin <code>AUTH</code></td><td>🔴 alto</td><td>una línea: <code>--requirepass</code></td></tr><tr><td><code>ufw deny</code> no aplicado a puertos publicados</td><td>🔴 alto</td><td>enlazar a <code>127.0.0.1</code>, o regla <code>DOCKER-USER</code></td></tr><tr><td>Bases de datos de respaldo en <code>127.0.0.1</code> (Langfuse)</td><td>✅ ninguno</td><td>ya correcto — copia este patrón</td></tr></tbody></table>\n<p>Ninguna de las soluciones tomó más de una línea de YAML. La parte costosa fue <em>saber qué línea</em> — y la única manera de saberlo es sondear tu propia caja en lugar de confiar en el diagrama en tu cabeza.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"solución-de-problemas\">Solución de problemas<a href=\"https://wec.wiline.com/docs/es/tutorials/harden-docker-networks-multi-agent/#soluci%C3%B3n-de-problemas\" class=\"hash-link\" aria-label=\"Enlace directo al Solución de problemas\" title=\"Enlace directo al Solución de problemas\" translate=\"no\">​</a></h2>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"agregué-una-regla-docker-user-y-nada-cambió\">Agregué una regla <code>DOCKER-USER</code> y nada cambió<a href=\"https://wec.wiline.com/docs/es/tutorials/harden-docker-networks-multi-agent/#agregu%C3%A9-una-regla-docker-user-y-nada-cambi%C3%B3\" class=\"hash-link\" aria-label=\"Enlace directo al agregué-una-regla-docker-user-y-nada-cambió\" title=\"Enlace directo al agregué-una-regla-docker-user-y-nada-cambió\" translate=\"no\">​</a></h3>\n<p>Dos causas habituales. Primero, <code>--dport</code> debe ser el puerto del <strong>contenedor</strong>, no el publicado — el DNAT ya reescribió el destino para cuando <code>DOCKER-USER</code> ve el paquete (en nuestro caso <code>3000</code>, no <code>3001</code>). Segundo, las conexiones existentes no se ven afectadas: la coincidencia <code>--ctstate NEW</code> solo toca las nuevas, así que una pestaña de navegador ya abierta sigue funcionando.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"habilité-ufw-y-perdí-mi-sesión-ssh\">Habilité UFW y perdí mi sesión SSH<a href=\"https://wec.wiline.com/docs/es/tutorials/harden-docker-networks-multi-agent/#habilit%C3%A9-ufw-y-perd%C3%AD-mi-sesi%C3%B3n-ssh\" class=\"hash-link\" aria-label=\"Enlace directo al Habilité UFW y perdí mi sesión SSH\" title=\"Enlace directo al Habilité UFW y perdí mi sesión SSH\" translate=\"no\">​</a></h3>\n<p><code>sudo ufw allow 22/tcp</code> <strong>antes de</strong> <code>ufw enable</code>, siempre. Si ya estás bloqueado, la mayoría de los proveedores te dan una consola serial/VNC — úsala para ejecutar <code>ufw disable</code>.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"internal-true-rompió-la-instalación-de-paquetes-de-mi-contenedor\"><code>internal: true</code> rompió la instalación de paquetes de mi contenedor<a href=\"https://wec.wiline.com/docs/es/tutorials/harden-docker-networks-multi-agent/#internal-true-rompi%C3%B3-la-instalaci%C3%B3n-de-paquetes-de-mi-contenedor\" class=\"hash-link\" aria-label=\"Enlace directo al internal-true-rompió-la-instalación-de-paquetes-de-mi-contenedor\" title=\"Enlace directo al internal-true-rompió-la-instalación-de-paquetes-de-mi-contenedor\" translate=\"no\">​</a></h3>\n<p>Eso es que está funcionando. Una red interna no tiene gateway, así que no hay salida en absoluto — sin <code>pip install</code>, sin DNS, sin llamar a una API externa. Los servicios de respaldo no deberían necesitar nada de eso; si un contenedor lo hace, también pertenece a la red de frontend.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"los-servicios-no-pueden-encontrarse-después-de-que-dividí-las-redes\">Los servicios no pueden encontrarse después de que dividí las redes<a href=\"https://wec.wiline.com/docs/es/tutorials/harden-docker-networks-multi-agent/#los-servicios-no-pueden-encontrarse-despu%C3%A9s-de-que-divid%C3%AD-las-redes\" class=\"hash-link\" aria-label=\"Enlace directo al Los servicios no pueden encontrarse después de que dividí las redes\" title=\"Enlace directo al Los servicios no pueden encontrarse después de que dividí las redes\" translate=\"no\">​</a></h3>\n<p>El DNS de Compose solo resuelve nombres <strong>dentro de una red compartida</strong>. Si <code>app</code> y <code>db</code> están en diferentes redes sin nada en común, <code>db</code> no se resolverá. La app tiene que ser miembro de ambas, como en la Solución 2.</p>\n<div class=\"skillComplete\"><span class=\"skillComplete__medal\" aria-hidden=\"true\">🎯</span><div class=\"skillComplete__text\"><div class=\"skillComplete__title\">Finished this tutorial?</div><div class=\"skillComplete__sub\">Mark it complete to earn <strong>Lock down Docker networks</strong> on your skill path.</div></div><button type=\"button\" class=\"skillComplete__btn\">Mark as complete ✓</button></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"qué-sigue\">¿Qué sigue?<a href=\"https://wec.wiline.com/docs/es/tutorials/harden-docker-networks-multi-agent/#qu%C3%A9-sigue\" class=\"hash-link\" aria-label=\"Enlace directo al ¿Qué sigue?\" title=\"Enlace directo al ¿Qué sigue?\" translate=\"no\">​</a></h2>\n<p>El endurecimiento de la red es la capa que evita que un contenedor comprometido se convierta en un host comprometido. Las siguientes capas, en el orden en que las haría: <strong>ejecutar contenedores como no-root</strong> (<code>user:</code> en compose) para que una ruptura comience con menos privilegios, y poner un <strong>proveedor de identidad frente a los puertos que debes exponer</strong> — una malla privada con\n<a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/\">NetBird</a> más autenticación es la combinación natural con todo lo anterior.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"lectura-adicional\">Lectura adicional<a href=\"https://wec.wiline.com/docs/es/tutorials/harden-docker-networks-multi-agent/#lectura-adicional\" class=\"hash-link\" aria-label=\"Enlace directo al Lectura adicional\" title=\"Enlace directo al Lectura adicional\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\"><a href=\"https://docs.docker.com/engine/network/packet-filtering-firewalls/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Documentación de Docker — filtrado de paquetes y firewalls</a></li>\n<li class=\"\"><a href=\"https://docs.docker.com/engine/network/drivers/bridge/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Documentación de Docker — opciones del controlador de red bridge</a></li>\n<li class=\"\"><a href=\"https://redis.io/docs/latest/operate/oss_and_stack/management/security/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Seguridad de Redis — autenticación</a></li>\n</ul>",
            "url": "https://wec.wiline.com/docs/es/tutorials/harden-docker-networks-multi-agent/",
            "title": "Tu firewall te está mintiendo: endurecimiento de redes Docker para sistemas multi-agente",
            "summary": "Una caja ejecutando cinco pilas de agentes, un firewall configurado para denegar todo, y servicios que aún responden desde Internet público. Investigamos un despliegue real, encontramos una base de datos sin contraseña, demostramos por qué UFW nunca ve el tráfico de Docker, y lo solucionamos de cuatro maneras — cada comando y resultado de una ejecución en vivo.",
            "date_modified": "2026-08-03T00:00:00.000Z",
            "author": {
                "name": "Rafael Fernandes",
                "url": "https://www.linkedin.com/in/rafaelmacariofernandes/"
            },
            "tags": [
                "docker",
                "seguridad",
                "redes",
                "auto-alojamiento",
                "agentes",
                "endurecimiento"
            ]
        },
        {
            "id": "https://wec.wiline.com/docs/es/tutorials/telegram-on-hermes/",
            "content_html": "<div class=\"tutorialHero\"><img class=\"tutorialHero__brand\" src=\"https://wec.wiline.com/docs/es/assets/images/hermesagent-c76bbe81610ae2e70b0950e0f96bec8a.png\" alt=\"Hermes Agent\"><span class=\"tutorialHero__plus\">+</span><svg viewBox=\"0 0 24 24\" fill=\"#26A5E4\" aria-label=\"Telegram\" class=\"tutorialHero__docker\"><path d=\"M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z\"></path></svg></div>\n<div class=\"skillTracker\" tabindex=\"0\" aria-label=\"Skill path: 0 of 2 skills earned\"><div class=\"skillTracker__ring\" style=\"--skillTracker-pct:0%\"><span class=\"skillTracker__ringLabel\">0<!-- -->/<!-- -->2</span></div><div class=\"skillTracker__panel\"><div class=\"skillTracker__header\"><span class=\"skillTracker__title\">🎯 Skill path</span><span class=\"skillTracker__count\">0<!-- -->/<!-- -->2<!-- --> earned</span></div><div class=\"skillTracker__series\">Self-hosting Hermes</div><ul class=\"skillTracker__steps\"><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/self-host-hermes-agent/\"><span class=\"skillTracker__dot\" data-state=\"locked\">1</span><span class=\"skillTracker__skill\" data-state=\"locked\">Agent with persistent memory</span></a></li><li><span class=\"skillTracker__step\"><span class=\"skillTracker__dot\" data-state=\"current\">🏆</span><span class=\"skillTracker__skill\" data-state=\"current\">Telegram on Hermes</span></span></li></ul><div class=\"skillTracker__footer\">Finish this tutorial, then mark it complete below to earn the skill.</div></div></div>\n<p>En <a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/self-host-hermes-agent/\">Parte 1</a> alojaste Hermes con\nmemoria persistente. Esta parte lo conecta al <strong>mismo bot de Telegram</strong> de la\n<a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/add-telegram-channel-openclaw/\">serie OpenClaw</a> — el chat que\ntus usuarios ya conocen sigue funcionando exactamente como antes, solo que con un agente diferente respondiendo detrás. No hay un nuevo bot que anunciar, ni un canal al que migrar a la gente.</p>\n<!-- -->\n<div class=\"theme-admonition theme-admonition-info admonition_xJq3 alert alert--info\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z\"></path></svg></span>Reproducibilidad</div><div class=\"admonitionContent_BuS1\"><p>Continúa en la misma instancia WEC que la Parte 1. El gateway de Hermes ya está\ninstalado y funcionando como un servicio de usuario systemd — esta parte es configuración,\nno instalación. Cada comando y error a continuación es de la ejecución real.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"por-qué-mantener-el-mismo-bot\">Por qué mantener el mismo bot<a href=\"https://wec.wiline.com/docs/es/tutorials/telegram-on-hermes/#por-qu%C3%A9-mantener-el-mismo-bot\" class=\"hash-link\" aria-label=\"Enlace directo al Por qué mantener el mismo bot\" title=\"Enlace directo al Por qué mantener el mismo bot\" translate=\"no\">​</a></h2>\n<p>El autoalojamiento solo vale la pena si no te ata al agente con el que comenzaste. El bot en sí — su nombre, su historial de chat, las personas que ya hablan con él — es el activo. El agente detrás de él es reemplazable. Cambiar OpenClaw por Hermes en el mismo bot prueba eso: misma puerta de entrada de Telegram, mismo hilo de conversación, nuevo cerebro.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"requisitos-previos\">Requisitos previos<a href=\"https://wec.wiline.com/docs/es/tutorials/telegram-on-hermes/#requisitos-previos\" class=\"hash-link\" aria-label=\"Enlace directo al Requisitos previos\" title=\"Enlace directo al Requisitos previos\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\">Hermes funcionando desde <a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/self-host-hermes-agent/\">Parte 1</a>, gateway\ninstalado como un servicio systemd.</li>\n<li class=\"\">Un token de bot de Telegram. Reutiliza el de la\n<a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/add-telegram-channel-openclaw/\">tutorial de Telegram de OpenClaw</a> a través de\n<strong>@BotFather → /mybots → tu bot → API Token</strong> — o crea uno nuevo con\n<strong>/newbot</strong> si no tienes el antiguo a mano. De cualquier manera, los pasos a continuación\nson idénticos.</li>\n</ul>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-1--ejecutar-el-asistente-de-configuración\">Paso 1 — Ejecutar el asistente de configuración<a href=\"https://wec.wiline.com/docs/es/tutorials/telegram-on-hermes/#paso-1--ejecutar-el-asistente-de-configuraci%C3%B3n\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 1 — Ejecutar el asistente de configuración\" title=\"Enlace directo al Paso 1 — Ejecutar el asistente de configuración\" translate=\"no\">​</a></h2>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">hermes gateway setup</span><br></div></code></pre></div></div>\n<p>Selecciona Telegram de la lista de plataformas, luego elige cómo conectarlo:</p>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">◆ Telegram</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  ¿Cómo te gustaría crear tu bot de Telegram?</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    [1] Automático (recomendado)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        Escanea un código QR → confirma en Telegram → listo.</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        No se necesita copiar y pegar el token.</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    [2] Manual</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        Crea un bot a través de @BotFather tú mismo y pega el token.</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Opción [1/2] [1]: 2</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  Crear un bot a través de @BotFather en Telegram</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Token de bot de Telegram: **********************************************</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">✓ Token de Telegram guardado</span><br></div></code></pre></div></div>\n<p>Toma la opción <strong>2</strong> — la opción 1 crea un bot completamente nuevo a través de un código QR, lo cual derrota el propósito si estás reutilizando uno existente.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-2--abrir-acceso-y-un-verdadero-problema\">Paso 2 — Abrir acceso (y un verdadero problema)<a href=\"https://wec.wiline.com/docs/es/tutorials/telegram-on-hermes/#paso-2--abrir-acceso-y-un-verdadero-problema\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 2 — Abrir acceso (y un verdadero problema)\" title=\"Enlace directo al Paso 2 — Abrir acceso (y un verdadero problema)\" translate=\"no\">​</a></h2>\n<p>A continuación, el asistente pregunta quiénes pueden usar el bot:</p>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">🔒 Seguridad: Restringir quién puede usar tu bot</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">   Para encontrar tu ID de usuario de Telegram:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">   1. Envía un mensaje a @userinfobot en Telegram</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">   2. Te responderá con tu ID numérico (por ejemplo, 123456789)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">IDs de usuario permitidos (separados por comas, deja vacío para acceso abierto):</span><br></div></code></pre></div></div>\n<p>Si no tienes tu ID a mano, déjalo en blanco y continúa — puedes restringir esto más tarde. Pero <strong>el aviso es engañoso</strong>: dejarlo vacío no abre el acceso por defecto. Los registros del gateway contaron la verdadera historia después del reinicio:</p>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">WARNING gateway.run: No se han configurado listas de permitidos de usuarios. Todos los usuarios no autorizados</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">serán denegados. Establece GATEWAY_ALLOW_ALL_USERS=true en ~/.hermes/.env para permitir</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">acceso abierto, o configura listas de permitidos de plataforma (por ejemplo,</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">TELEGRAM_ALLOWED_USERS=tu_id).</span><br></div></code></pre></div></div>\n<p>Una lista de permitidos vacía significa <strong>denegar a todos</strong>, no acceso abierto. Si deseas acceso abierto (bien para un bot personal, no para nada público), necesitas un paso más:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token builtin class-name\">echo</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'GATEWAY_ALLOW_ALL_USERS=true'</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;&gt;</span><span class=\"token plain\"> ~/.hermes/.env</span><br></div></code></pre></div></div>\n<p>El asistente también pregunta por un <strong>Canal Principal</strong> — donde Hermes entrega resultados de cron\ny mensajes entre plataformas. Déjalo en blanco también; puedes configurarlo más tarde\ndesde dentro del chat con <code>/sethome</code>.</p>\n<p>Termina el asistente y deja que reinicie el gateway:</p>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">¿Reiniciar el gateway para recoger cambios? [Y/n]: Y</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">✓ Servicio de usuario reiniciado</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Terminal mostrando la ejecución completa del asistente — token guardado, la advertencia de la lista de permitidos, y el reinicio del gateway\" src=\"https://wec.wiline.com/docs/es/assets/images/hermes-gateway-setup-wizard-7293ce9021ee147b93bad2dcb7a72a53.png\" width=\"603\" height=\"949\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 1.</strong> Todo el asistente, de principio a fin — token, la advertencia de la lista de permitidos, y el reinicio.</p>\n<p>Si agregaste <code>GATEWAY_ALLOW_ALL_USERS</code> después de que el asistente ya reinició\nuna vez (como arriba), reinicia de nuevo para que recoja el cambio en <code>.env</code>:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">systemctl </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--user</span><span class=\"token plain\"> restart hermes-gateway.service</span><br></div></code></pre></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-3--verificar\">Paso 3 — Verificar<a href=\"https://wec.wiline.com/docs/es/tutorials/telegram-on-hermes/#paso-3--verificar\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 3 — Verificar\" title=\"Enlace directo al Paso 3 — Verificar\" translate=\"no\">​</a></h2>\n<p>Envía un mensaje a tu bot. <strong>No a @BotFather</strong> — a tu bot, por el nombre de usuario que le diste\ncuando lo creaste. Es fácil confundirse: BotFather responde todo con\nsu propio menú de comandos fijo, así que si ves eso en lugar de una respuesta real,\nestás en el chat equivocado.</p>\n<p>El primer mensaje puede recibir un empujón del sistema en lugar de una respuesta de chat — eso es\nHermes notando que aún no hay un canal principal, no un fallo:</p>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">📬 No se ha establecido un canal principal para Telegram. Un canal principal es donde Hermes</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">entrega resultados de trabajos cron y mensajes entre plataformas.</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Escribe /sethome para hacer de este chat tu canal principal, o ignora para omitir.</span><br></div></code></pre></div></div>\n<p>Envía otro mensaje y responde de verdad:</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Chat de Telegram mostrando al bot respondiendo &amp;quot;¡Hola! ¿Cómo puedo ayudar?&amp;quot; a través de Hermes\" src=\"https://wec.wiline.com/docs/es/assets/images/hermes-telegram-verified-17c22677d5e1e6a1d6147099b58fdc53.png\" width=\"1320\" height=\"2868\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 2.</strong> El mismo bot de la serie OpenClaw, ahora respondiendo a través de Hermes.</p>\n<p>Pregúntale algo con profundidad real, y entrega una respuesta estructurada real\n— no solo una respuesta estándar:</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Chat de Telegram mostrando al bot explicando la dispersión de Rayleigh en respuesta a &amp;quot;¿Por qué es el cielo azul?&amp;quot;\" src=\"https://wec.wiline.com/docs/es/assets/images/hermes-telegram-sky-blue-139494c527c4075440bf7afa25c14039.png\" width=\"1320\" height=\"2868\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 3.</strong> Mismo agente, una pregunta real — explicación completa, no una línea.</p>\n<p>Mismo bot, mismo historial de chat, nuevo agente.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"solución-de-problemas-real\">Solución de problemas (real)<a href=\"https://wec.wiline.com/docs/es/tutorials/telegram-on-hermes/#soluci%C3%B3n-de-problemas-real\" class=\"hash-link\" aria-label=\"Enlace directo al Solución de problemas (real)\" title=\"Enlace directo al Solución de problemas (real)\" translate=\"no\">​</a></h2>\n<p><strong>\"No se han configurado listas de permitidos de usuarios. Todos los usuarios no autorizados serán denegados.\"</strong> —\ndejar el aviso de la lista de permitidos vacío no significa acceso abierto; significa\ndenegar a todos. Establece <code>GATEWAY_ALLOW_ALL_USERS=true</code> en <code>~/.hermes/.env</code> y reinicia\nel gateway.</p>\n<p><strong>\"Conflicto de sondeo de Telegram... asegúrate de que solo una instancia de bot esté\nejecutándose\"</strong> — aparece durante unos segundos justo después de un reinicio, mientras\nlos servidores de Telegram terminan de liberar la sesión anterior. Intenta de nuevo y\nse limpia solo dentro de los 20 segundos; no se necesita acción a menos que siga\nrepitiéndose más allá del 5to intento, lo que significa que otro proceso realmente está sondeando\nel mismo token.</p>\n<p><strong>El bot no responde en absoluto</strong> — verifica que estás enviando un mensaje al bot mismo, no\na @BotFather. BotFather siempre responde con su propia lista de comandos independientemente de\nlo que le envíes.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"qué-sigue\">Qué sigue<a href=\"https://wec.wiline.com/docs/es/tutorials/telegram-on-hermes/#qu%C3%A9-sigue\" class=\"hash-link\" aria-label=\"Enlace directo al Qué sigue\" title=\"Enlace directo al Qué sigue\" translate=\"no\">​</a></h2>\n<p>El bot responde — lo siguiente es ponerlo en un horario: un informe matutino o un\nchequeo de salud del servicio entregado a este mismo chat, sin necesidad de sondeo.</p>\n<div class=\"skillComplete\"><span class=\"skillComplete__medal\" aria-hidden=\"true\">🎯</span><div class=\"skillComplete__text\"><div class=\"skillComplete__title\">Finished this tutorial?</div><div class=\"skillComplete__sub\">Mark it complete to earn <strong>Telegram on Hermes</strong> on your skill path.</div></div><button type=\"button\" class=\"skillComplete__btn\">Mark as complete ✓</button></div>",
            "url": "https://wec.wiline.com/docs/es/tutorials/telegram-on-hermes/",
            "title": "Migrar el Bot de Telegram de OpenClaw a Hermes",
            "summary": "Mover el mismo bot de Telegram de la serie OpenClaw a Hermes — mismo chat, mismos usuarios, un agente diferente respondiendo detrás. Un verdadero asistente de configuración, un verdadero problema de lista de permitidos, y prueba de que responde.",
            "date_modified": "2026-07-29T00:00:00.000Z",
            "author": {
                "name": "Rafael Fernandes",
                "url": "https://www.linkedin.com/in/rafaelmacariofernandes/"
            },
            "tags": [
                "ai",
                "autoalojamiento",
                "hermes",
                "telegram",
                "mensajería"
            ]
        },
        {
            "id": "https://wec.wiline.com/docs/es/tutorials/web-search-wiline-inference/",
            "content_html": "<div class=\"tutorialHero\"><img class=\"tutorialHero__brand\" src=\"https://wec.wiline.com/docs/es/assets/images/searxng-mark-1b536bb949960590cea598d56becda08.png\" alt=\"SearXNG\"><span class=\"tutorialHero__plus\">+</span><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"242\" height=\"255\" fill=\"none\" viewBox=\"0 0 242 255\" class=\"tutorialHero__wiline\"><path fill=\"#4E8EF7\" d=\"M56.711 11.293A195 195 0 0 1 41.899 4.79c4.788 1.85 9.71 4.028 14.812 6.504M73.807 29.35c-1.302-1.446-2.666-3.233-4.02-5.38 1.326 1.44 2.642 3.24 4.02 5.38M69.761 23.354c-.832-.585-1.752-1.472-2.558-2.714.9.567 1.685 1.49 2.558 2.714M62.97 15.774a10.7 10.7 0 0 1-3.158-1.841c.993.299 1.986.956 3.159 1.841M59.715 13.346c-.818-.194-1.782-.674-2.829-1.494.84.175 1.761.691 2.83 1.494M39.317 3.782c-.524.192-1.413.302-2.455.14.594-.255 1.342-.238 2.455-.14M74.906 31.339c-.334-.153-.73-.61-1.117-1.398.358.143.706.619 1.117 1.398M66.024 18.878c-.239.1-.557.05-.752-.276.35-.201.532-.069.752.276M71.773 150.625c3.363 7.291 7.185 14.411 9.983 21.914 4.474 11.997 11.167 20.832 24.296 24.757 10.62 3.175 20.369 1.795 30.093-1.816 9.937-3.689 15.165-11.978 18.501-21.541 4.349-12.466 9.385-24.443 19.668-33.446 11.32-9.909 24.639-14.788 39.429-15.63 8.3-.473 16.649-.081 25.444.444.472 15.673.385 30.825.55 45.974.038 3.499-1.049 4.719-4.557 4.543-4.982-.25-9.987-.079-14.98.009-.209.004-.4.991-.6 1.521-23.106 1.695-37.691 15.22-47.961 34.651-3.387 6.408-5.913 13.38-10.013 19.273-4.368 6.277-10.134 11.582-15.853 17.196-6.439 1.421-12.244 3.342-18.202 4.416-12.627 2.274-24.661.457-35.764-6.356-1.028-.631-2.395-.709-3.604-1.043 0 0-.117-.11-.047-.495-.818-.973-1.706-1.562-2.593-2.15 0 0 .09-.044.05-.335-.513-.545-.988-.798-1.463-1.052 0 0-.07-.123.012-.492-.561-.894-1.205-1.421-1.848-1.947 0 0 .134-.068.102-.28-.321-.442-.61-.671-.898-.901 0 0 .086-.007.074-.336-2.05-3.429-4.089-6.529-6.127-9.629 0 0 .094-.017.123-.365-3.013-6.117-6.056-11.886-9.1-17.655 0 0 .096-.01.088-.342-1.978-4.073-3.89-7.848-5.997-11.51-.246-.428-1.572-.235-2.398-.329 0 0-.226-.202-.114-.613-.213-.499-.537-.587-.861-.674 0 0-.032-.101.02-.455-.62-.846-1.291-1.339-1.964-1.832 0 0 .133-.083.119-.372-2.476-4.624-5.645-8.048-10.914-8.777 0 0 .108-.031.01-.326-1.673-1.444-3.105-2.918-4.85-3.688-9.751-4.308-20.013-5.643-30.63-5.23-7.305.285-7.316.051-7.317-7.446q-.002-18.496-.002-36.992v-3.57h36.106s-.08.019.133.283c1.43.415 2.646.567 3.863.719 0 0-.077.022.113.296 1.804.591 3.417.908 5.03 1.225 0 0 .139-.037.152.369 1.592.962 3.17 1.517 4.748 2.073 0 0-.091.027.052.267.735.415 1.328.59 1.92.765 0 0-.076.033.053.246.477.325.825.438 1.173.551 0 0-.149.018-.07.246.418.239.757.251 1.096.263 0 0 .23.058.189.465 1.522 1.319 3.084 2.231 4.647 3.143 0 0-.105.033-.015.316 2.723 2.764 5.356 5.244 7.989 7.724 0 0-.104.062-.094.352 1.01 1.401 2.01 2.512 3.01 3.624\"></path><path fill=\"#4E8EF7\" d=\"M214.77 52.819c-3.343.846-6.723 1.574-10.024 2.562-11.388 3.408-20.567 10.142-27.166 19.865-5.097 7.511-9.621 15.483-13.685 23.608-6.809 13.613-15.725 24.809-31.343 28.198-24.707 5.362-45.22-.126-57.852-24.564C69.466 92.363 63.8 82.375 57.349 73c-3.369-4.896-8.866-8.326-13.679-13.041-1.11-1.409-1.81-2.472-2.794-2.937-10.697-5.056-21.87-7.3-33.686-5.344-3.877.641-5.72-.297-5.615-4.86.294-12.816.26-25.646.019-38.464C1.509 3.874 3.15 2.5 7.429 2.716c9.774.492 19.564.678 29.347.989 0 0-.076.026.082.257 1.046.122 1.935.012 2.825-.098.575.043 1.15.086 2.038.692 5.265 2.72 10.218 4.877 15.17 7.034 0 0-.108.03-.016.317 1.058.765 2.022 1.245 2.987 1.724 0 0-.113.048-.082.354 1.144.876 2.258 1.446 3.372 2.017 0 0 .18.145.097.506.223.404.53.447.837.49 0 0 .214.13.144.504.244.426.558.477.873.528 0 0 .171.124.108.485.256.412.574.464.893.515 0 0 .167.127.105.493.265.437.592.509.919.58 0 0 .078.108.019.5.861 1.28 1.782 2.166 2.702 3.053 0 0-.101.061-.087.368 1.379 2.093 2.743 3.88 4.107 5.666 0 0-.094.011-.092.297.4.742.795 1.2 1.19 1.656 0 0-.121.02-.165.337.685 1.432 1.413 2.549 2.14 3.665 0 0-.11.024-.129.299.326.776.67 1.277 1.014 1.78 0 0-.084-.005-.1.367.933 4.079.311 8.497 4.648 10.67 4.69 12.164 12.107 21.794 25.852 23.497 19.618 2.431 35.321.062 45.299-24.554 3.398-8.383 7.099-16.902 12.232-24.264 8.353-11.979 21.046-17.795 35.832-20.32 1.666.417 2.649.651 3.624.622 9.93-.298 19.865-.513 29.785-1.032 3.818-.2 4.825 1.198 4.746 4.853-.218 9.967.004 19.944-.109 29.915-.052 4.583 1.149 10.606-1.239 13.302-2.044 2.308-8.394.86-12.842.967-3.82.092-7.644.036-11.467.045.065.31.097.633.213.922.034.084.306.072.469.105M37.585 127.354c-11.585.344-23.368.344-35.907.344v3.57q0 18.496.002 36.992c0 7.497.012 7.731 7.318 7.446 10.616-.413 20.878.922 30.63 5.23 1.743.77 3.176 2.244 4.84 3.744-13.655-7.873-28.93-7.767-44.468-7.398v-52.503c12.516.308 25.063-1.222 37.585 2.575\"></path><path fill=\"#4E8EF7\" d=\"M36.619 3.401c-9.626-.007-19.415-.193-29.19-.685C3.151 2.5 1.51 3.874 1.594 8.354c.241 12.818.275 25.648-.018 38.464-.105 4.563 1.737 5.501 5.614 4.86 11.816-1.956 22.99.288 33.686 5.344.984.465 1.685 1.529 2.616 2.66-13.39-7.538-28.284-7.343-43.479-6.78V0C12.314 1.045 24.388 2.071 36.62 3.401M215.232 52.852c-.625-.066-.897-.054-.931-.138-.116-.29-.148-.612-.212-.922 3.822-.01 7.646.047 11.467-.045 4.448-.108 10.797 1.341 12.842-.967 2.387-2.696 1.186-8.719 1.238-13.302.114-9.971-.109-19.948.109-29.915.08-3.655-.928-5.052-4.746-4.853-9.919.519-19.855.734-29.785 1.032-.975.03-1.958-.205-3.228-.58C214.559 1.945 227.423.991 240.73.003v52.882c-8.276 0-16.656 0-25.498-.033M220.071 177.398c-.271-.573-.08-1.561.129-1.565 4.993-.088 9.998-.259 14.98-.009 3.508.176 4.595-1.044 4.557-4.543-.165-15.149-.078-30.301-.088-45.911.458.372 1.32 1.202 1.324 2.036.079 16.424.059 32.848.059 50.036-6.984 0-13.737 0-20.961-.044M88.39 245.741c1.022.084 2.39.162 3.417.793 11.102 6.813 23.137 8.63 35.763 6.356 5.959-1.073 11.764-2.995 17.871-4.34-5.612 2.132-11.343 5.166-17.325 5.79-13.898 1.451-27.803 1.213-39.725-8.599M66.556 210.212c2.975 5.411 6.018 11.18 9.02 17.344-3.011-5.4-5.982-11.193-9.02-17.344M58.341 197.934c.666-.167 1.992-.36 2.238.068 2.107 3.662 4.018 7.437 5.985 11.557-2.69-3.535-5.376-7.449-8.223-11.625M44.637 185.241c5.099.438 8.268 3.862 10.737 8.564-3.518-2.513-7.043-5.393-10.737-8.564M82.29 48.422c-4.252-1.834-3.63-6.252-4.548-10.37 1.508 3.069 2.986 6.55 4.548 10.37M68.757 146.326c-2.533-2.157-5.166-4.637-7.882-7.455 2.539 2.151 5.16 4.641 7.882 7.455M75.532 228.226a122 122 0 0 1 6.047 9.319c-1.993-2.742-3.986-5.854-6.047-9.319M71.695 150.312c-.922-.799-1.921-1.91-2.906-3.366.952.788 1.89 1.921 2.906 3.366M46.663 130.007c-1.353-.103-2.966-.42-4.767-1.051 1.377.07 2.941.454 4.767 1.051M51.647 132.369a13.1 13.1 0 0 1-4.501-1.77c1.48.227 2.902.852 4.5 1.77M60.724 138.321a17.7 17.7 0 0 1-4.395-2.781c1.501.609 2.869 1.551 4.395 2.781M76.889 35.31c-.674-.781-1.402-1.898-2.068-3.39.711.768 1.362 1.912 2.068 3.39M85.668 243.124c.782.31 1.67.899 2.423 1.825-.862-.29-1.59-.919-2.423-1.825M41.596 128.372c-1.033.176-2.25.024-3.68-.432 1.023-.169 2.26-.032 3.68.432M55.367 194.37c.568.222 1.24.715 1.808 1.548-.637-.199-1.17-.738-1.808-1.548M82.397 239.295c.56.251 1.204.778 1.71 1.628-.633-.235-1.13-.794-1.71-1.628M84.291 241.679c.334.034.809.287 1.298.875-.376.004-.766-.326-1.298-.875M53.64 133.426c-.438.094-1.03-.081-1.764-.543.441-.101 1.026.086 1.764.543M77.805 37.409c-.322-.188-.666-.69-.978-1.52.34.182.648.693.978 1.52M81.588 238.108c.218-.039.507.19.823.707-.233.045-.493-.196-.823-.707M55.906 134.832c-.198.157-.537.145-.92-.161.262-.29.537-.195.92.161M57.276 196.552c.254-.079.578.009.756.361-.377.171-.557.018-.756-.361M54.888 134.25c-.215.129-.563.017-1.038-.351.217-.133.56-.012 1.038.351M64.014 16.858a.545.545 0 0 1-.71-.247c.344-.19.51-.067.71.247M65.027 17.882a.574.574 0 0 1-.734-.272c.35-.193.525-.063.734.272M67.045 19.939c-.244.093-.57.021-.777-.329.358-.192.55-.044.777.329\"></path></svg></div>\n<div class=\"skillTracker\" tabindex=\"0\" aria-label=\"Skill path: 0 of 8 skills earned\"><div class=\"skillTracker__ring\" style=\"--skillTracker-pct:0%\"><span class=\"skillTracker__ringLabel\">0<!-- -->/<!-- -->8</span></div><div class=\"skillTracker__panel\"><div class=\"skillTracker__header\"><span class=\"skillTracker__title\">🎯 Skill path</span><span class=\"skillTracker__count\">0<!-- -->/<!-- -->8<!-- --> earned</span></div><div class=\"skillTracker__series\">AI evals &amp; observability</div><ul class=\"skillTracker__steps\"><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">1</span><span class=\"skillTracker__skill\" data-state=\"locked\">Prove a model works</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">2</span><span class=\"skillTracker__skill\" data-state=\"locked\">Trustworthy JSON</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/synthetic-eval-datasets-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">3</span><span class=\"skillTracker__skill\" data-state=\"locked\">Real test data at scale</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/\"><span class=\"skillTracker__dot\" data-state=\"locked\">4</span><span class=\"skillTracker__skill\" data-state=\"locked\">Observe &amp; score production</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">5</span><span class=\"skillTracker__skill\" data-state=\"locked\">RAG, end to end</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/deepeval-regression-testing-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">6</span><span class=\"skillTracker__skill\" data-state=\"locked\">Catch regressions in CI</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/component-tracing-agent-tool-calls-wiline/\"><span class=\"skillTracker__dot\" data-state=\"locked\">7</span><span class=\"skillTracker__skill\" data-state=\"locked\">Trace &amp; debug agent tool calls</span></a></li><li><span class=\"skillTracker__step\"><span class=\"skillTracker__dot\" data-state=\"current\">🏆</span><span class=\"skillTracker__skill\" data-state=\"current\">Add live web search</span></span></li></ul><div class=\"skillTracker__footer\">Finish this tutorial, then mark it complete below to earn the skill.</div></div></div>\n<p>Supón que has construido un chatbot de soporte o un asistente RAG en WEC. Es sólido en tus documentos y\nen lo que el modelo ya sabe, pero pregúntale algo <em>actual</em> (\"¿cuál es la última\nversión de X?\", precios de hoy, un cambio reciente) y o bien adivina o te dice que su\nconocimiento está desactualizado. Para cualquier cosa que necesite estar al día, eso es una verdadera brecha.</p>\n<p>WEC Inference ahora tiene una <strong>herramienta de búsqueda web</strong> integrada. Agrégala a una llamada normal\n<code>/v1/chat/completions</code> y el modelo puede buscar cosas en la web en vivo: la búsqueda\nse realiza en la propia infraestructura de WiLine (SearXNG), por lo que nada se envía a un proveedor de búsqueda de terceros. Vamos a probarlo.</p>\n<!-- -->\n<div class=\"theme-admonition theme-admonition-info admonition_xJq3 alert alert--info\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z\"></path></svg></span>Reproducibilidad</div><div class=\"admonitionContent_BuS1\"><p>Las llamadas llegan a <code>https://inference.wiline.com/v1/chat/completions</code> con tu clave API de WEC Inference,\nmodelo <code>Qwen3.5-9B</code>. No hay nada que instalar: la herramienta es parte de la API.</p></div></div>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"sin-búsqueda-web\">Sin búsqueda web<a href=\"https://wec.wiline.com/docs/es/tutorials/web-search-wiline-inference/#sin-b%C3%BAsqueda-web\" class=\"hash-link\" aria-label=\"Enlace directo al Sin búsqueda web\" title=\"Enlace directo al Sin búsqueda web\" translate=\"no\">​</a></h2>\n<p>Una llamada normal, preguntando algo reciente:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-sS</span><span class=\"token plain\"> https://inference.wiline.com/v1/chat/completions </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Authorization: Bearer </span><span class=\"token string variable\" style=\"color:#36acaa\">$WEC_API_KEY</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Content-Type: application/json\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'{</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    \"model\": \"Qwen3.5-9B\",</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    \"messages\": [{\"role\": \"user\", \"content\": \"¿Cuál es la última versión estable de Docker Engine, y cuándo fue lanzada?\"}]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  }'</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El modelo respondiendo solo con datos de entrenamiento — una versión desactualizada\" src=\"https://wec.wiline.com/docs/es/assets/images/websearch-without-aeaee1a5d9a326ccb92d56a8ec08bdc5.png\" width=\"1099\" height=\"940\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 1.</strong> Sin herramienta, el modelo no se compromete a una versión — dice que su conocimiento está\nlimitado a su fecha de corte de entrenamiento y te señala las notas de la versión en su lugar.</p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"con-búsqueda-web\">Con búsqueda web<a href=\"https://wec.wiline.com/docs/es/tutorials/web-search-wiline-inference/#con-b%C3%BAsqueda-web\" class=\"hash-link\" aria-label=\"Enlace directo al Con búsqueda web\" title=\"Enlace directo al Con búsqueda web\" translate=\"no\">​</a></h2>\n<p>La <strong>misma</strong> solicitud, más una herramienta <code>web_search</code> y <code>tool_choice: auto</code>:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-sS</span><span class=\"token plain\"> https://inference.wiline.com/v1/chat/completions </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Authorization: Bearer </span><span class=\"token string variable\" style=\"color:#36acaa\">$WEC_API_KEY</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Content-Type: application/json\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'{</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    \"model\": \"Qwen3.5-9B\",</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    \"messages\": [{\"role\": \"user\", \"content\": \"¿Cuál es la última versión estable de Docker Engine, y cuándo fue lanzada?\"}],</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    \"tools\": [{</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      \"type\": \"function\",</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      \"function\": {</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        \"name\": \"litellm_web_search\",</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        \"description\": \"Buscar en la web información actual\",</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        \"parameters\": {</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">          \"type\": \"object\",</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">          \"properties\": {\"query\": {\"type\": \"string\", \"description\": \"La consulta de búsqueda\"}},</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">          \"required\": [\"query\"]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    }],</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    \"tool_choice\": \"auto\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  }'</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El modelo respondiendo con la versión actual, fundamentada en resultados web\" src=\"https://wec.wiline.com/docs/es/assets/images/websearch-with-ae28b4b9dbd33effc4676a36ce37a4e0.png\" width=\"1100\" height=\"731\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 2.</strong> Mismo modelo, misma pregunta, más la herramienta — ahora devuelve una versión concreta\ny fecha de lanzamiento obtenida de la web en vivo. El modelo decidió buscar, WEC ejecutó la consulta,\ny alimentó los resultados antes de responder.</p>\n<p>Esa es toda la función: una herramienta en el cuerpo de la solicitud, respuestas actuales saliendo.</p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"una-cosa-a-tener-en-cuenta-tokens\">Una cosa a tener en cuenta: tokens<a href=\"https://wec.wiline.com/docs/es/tutorials/web-search-wiline-inference/#una-cosa-a-tener-en-cuenta-tokens\" class=\"hash-link\" aria-label=\"Enlace directo al Una cosa a tener en cuenta: tokens\" title=\"Enlace directo al Una cosa a tener en cuenta: tokens\" translate=\"no\">​</a></h2>\n<p>La búsqueda web inyecta los resultados en tu aviso, por lo que una llamada con búsqueda cuesta más tokens que\nuna simple (en nuestra prueba, los tokens de aviso pasaron de ~26 a ~2,776). Mantén <code>tool_choice: auto</code>\npara que el modelo solo busque cuando realmente lo necesita — omitirá la búsqueda para preguntas que\nya puede responder.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"cuándo-usarlo\">Cuándo usarlo<a href=\"https://wec.wiline.com/docs/es/tutorials/web-search-wiline-inference/#cu%C3%A1ndo-usarlo\" class=\"hash-link\" aria-label=\"Enlace directo al Cuándo usarlo\" title=\"Enlace directo al Cuándo usarlo\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\"><strong>Sí:</strong> versiones actuales, precios, noticias, cualquier cosa después de la fecha de corte de entrenamiento del modelo.</li>\n<li class=\"\"><strong>Omitir:</strong> conocimiento estable que el modelo ya tiene — <code>auto</code> se encarga de eso por ti.</li>\n</ul>\n<p>Dirígete a un asistente RAG o un chatbot y puede mantenerse actualizado sin que tengas que configurar un\nservicio de búsqueda separado.</p>\n<div class=\"skillComplete\"><span class=\"skillComplete__medal\" aria-hidden=\"true\">🎯</span><div class=\"skillComplete__text\"><div class=\"skillComplete__title\">Finished this tutorial?</div><div class=\"skillComplete__sub\">Mark it complete to earn <strong>Add live web search</strong> on your skill path.</div></div><button type=\"button\" class=\"skillComplete__btn\">Mark as complete ✓</button></div>",
            "url": "https://wec.wiline.com/docs/es/tutorials/web-search-wiline-inference/",
            "title": "Agrega búsqueda web a tus llamadas de WEC Inference",
            "summary": "Tu asistente RAG responde bien a partir de tus documentos, pero no sobre nada reciente. WEC Inference ahora tiene una herramienta de búsqueda web integrada: agrégala a una llamada de chat y el modelo obtiene información actual. Una prueba rápida antes/después.",
            "date_modified": "2026-07-23T00:00:00.000Z",
            "author": {
                "name": "Rafael Fernandes",
                "url": "https://www.linkedin.com/in/rafaelmacariofernandes/"
            },
            "tags": [
                "ai",
                "inference",
                "web-search",
                "tool-use"
            ]
        },
        {
            "id": "https://wec.wiline.com/docs/es/tutorials/component-tracing-agent-tool-calls-wiline/",
            "content_html": "<div class=\"tutorialHero\"><img class=\"tutorialHero__langfuse\" src=\"data:image/webp;base64,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\" alt=\"Langfuse\"><span class=\"tutorialHero__plus\">+</span><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"242\" height=\"255\" fill=\"none\" viewBox=\"0 0 242 255\" class=\"tutorialHero__wiline\"><path fill=\"#4E8EF7\" d=\"M56.711 11.293A195 195 0 0 1 41.899 4.79c4.788 1.85 9.71 4.028 14.812 6.504M73.807 29.35c-1.302-1.446-2.666-3.233-4.02-5.38 1.326 1.44 2.642 3.24 4.02 5.38M69.761 23.354c-.832-.585-1.752-1.472-2.558-2.714.9.567 1.685 1.49 2.558 2.714M62.97 15.774a10.7 10.7 0 0 1-3.158-1.841c.993.299 1.986.956 3.159 1.841M59.715 13.346c-.818-.194-1.782-.674-2.829-1.494.84.175 1.761.691 2.83 1.494M39.317 3.782c-.524.192-1.413.302-2.455.14.594-.255 1.342-.238 2.455-.14M74.906 31.339c-.334-.153-.73-.61-1.117-1.398.358.143.706.619 1.117 1.398M66.024 18.878c-.239.1-.557.05-.752-.276.35-.201.532-.069.752.276M71.773 150.625c3.363 7.291 7.185 14.411 9.983 21.914 4.474 11.997 11.167 20.832 24.296 24.757 10.62 3.175 20.369 1.795 30.093-1.816 9.937-3.689 15.165-11.978 18.501-21.541 4.349-12.466 9.385-24.443 19.668-33.446 11.32-9.909 24.639-14.788 39.429-15.63 8.3-.473 16.649-.081 25.444.444.472 15.673.385 30.825.55 45.974.038 3.499-1.049 4.719-4.557 4.543-4.982-.25-9.987-.079-14.98.009-.209.004-.4.991-.6 1.521-23.106 1.695-37.691 15.22-47.961 34.651-3.387 6.408-5.913 13.38-10.013 19.273-4.368 6.277-10.134 11.582-15.853 17.196-6.439 1.421-12.244 3.342-18.202 4.416-12.627 2.274-24.661.457-35.764-6.356-1.028-.631-2.395-.709-3.604-1.043 0 0-.117-.11-.047-.495-.818-.973-1.706-1.562-2.593-2.15 0 0 .09-.044.05-.335-.513-.545-.988-.798-1.463-1.052 0 0-.07-.123.012-.492-.561-.894-1.205-1.421-1.848-1.947 0 0 .134-.068.102-.28-.321-.442-.61-.671-.898-.901 0 0 .086-.007.074-.336-2.05-3.429-4.089-6.529-6.127-9.629 0 0 .094-.017.123-.365-3.013-6.117-6.056-11.886-9.1-17.655 0 0 .096-.01.088-.342-1.978-4.073-3.89-7.848-5.997-11.51-.246-.428-1.572-.235-2.398-.329 0 0-.226-.202-.114-.613-.213-.499-.537-.587-.861-.674 0 0-.032-.101.02-.455-.62-.846-1.291-1.339-1.964-1.832 0 0 .133-.083.119-.372-2.476-4.624-5.645-8.048-10.914-8.777 0 0 .108-.031.01-.326-1.673-1.444-3.105-2.918-4.85-3.688-9.751-4.308-20.013-5.643-30.63-5.23-7.305.285-7.316.051-7.317-7.446q-.002-18.496-.002-36.992v-3.57h36.106s-.08.019.133.283c1.43.415 2.646.567 3.863.719 0 0-.077.022.113.296 1.804.591 3.417.908 5.03 1.225 0 0 .139-.037.152.369 1.592.962 3.17 1.517 4.748 2.073 0 0-.091.027.052.267.735.415 1.328.59 1.92.765 0 0-.076.033.053.246.477.325.825.438 1.173.551 0 0-.149.018-.07.246.418.239.757.251 1.096.263 0 0 .23.058.189.465 1.522 1.319 3.084 2.231 4.647 3.143 0 0-.105.033-.015.316 2.723 2.764 5.356 5.244 7.989 7.724 0 0-.104.062-.094.352 1.01 1.401 2.01 2.512 3.01 3.624\"></path><path fill=\"#4E8EF7\" d=\"M214.77 52.819c-3.343.846-6.723 1.574-10.024 2.562-11.388 3.408-20.567 10.142-27.166 19.865-5.097 7.511-9.621 15.483-13.685 23.608-6.809 13.613-15.725 24.809-31.343 28.198-24.707 5.362-45.22-.126-57.852-24.564C69.466 92.363 63.8 82.375 57.349 73c-3.369-4.896-8.866-8.326-13.679-13.041-1.11-1.409-1.81-2.472-2.794-2.937-10.697-5.056-21.87-7.3-33.686-5.344-3.877.641-5.72-.297-5.615-4.86.294-12.816.26-25.646.019-38.464C1.509 3.874 3.15 2.5 7.429 2.716c9.774.492 19.564.678 29.347.989 0 0-.076.026.082.257 1.046.122 1.935.012 2.825-.098.575.043 1.15.086 2.038.692 5.265 2.72 10.218 4.877 15.17 7.034 0 0-.108.03-.016.317 1.058.765 2.022 1.245 2.987 1.724 0 0-.113.048-.082.354 1.144.876 2.258 1.446 3.372 2.017 0 0 .18.145.097.506.223.404.53.447.837.49 0 0 .214.13.144.504.244.426.558.477.873.528 0 0 .171.124.108.485.256.412.574.464.893.515 0 0 .167.127.105.493.265.437.592.509.919.58 0 0 .078.108.019.5.861 1.28 1.782 2.166 2.702 3.053 0 0-.101.061-.087.368 1.379 2.093 2.743 3.88 4.107 5.666 0 0-.094.011-.092.297.4.742.795 1.2 1.19 1.656 0 0-.121.02-.165.337.685 1.432 1.413 2.549 2.14 3.665 0 0-.11.024-.129.299.326.776.67 1.277 1.014 1.78 0 0-.084-.005-.1.367.933 4.079.311 8.497 4.648 10.67 4.69 12.164 12.107 21.794 25.852 23.497 19.618 2.431 35.321.062 45.299-24.554 3.398-8.383 7.099-16.902 12.232-24.264 8.353-11.979 21.046-17.795 35.832-20.32 1.666.417 2.649.651 3.624.622 9.93-.298 19.865-.513 29.785-1.032 3.818-.2 4.825 1.198 4.746 4.853-.218 9.967.004 19.944-.109 29.915-.052 4.583 1.149 10.606-1.239 13.302-2.044 2.308-8.394.86-12.842.967-3.82.092-7.644.036-11.467.045.065.31.097.633.213.922.034.084.306.072.469.105M37.585 127.354c-11.585.344-23.368.344-35.907.344v3.57q0 18.496.002 36.992c0 7.497.012 7.731 7.318 7.446 10.616-.413 20.878.922 30.63 5.23 1.743.77 3.176 2.244 4.84 3.744-13.655-7.873-28.93-7.767-44.468-7.398v-52.503c12.516.308 25.063-1.222 37.585 2.575\"></path><path fill=\"#4E8EF7\" d=\"M36.619 3.401c-9.626-.007-19.415-.193-29.19-.685C3.151 2.5 1.51 3.874 1.594 8.354c.241 12.818.275 25.648-.018 38.464-.105 4.563 1.737 5.501 5.614 4.86 11.816-1.956 22.99.288 33.686 5.344.984.465 1.685 1.529 2.616 2.66-13.39-7.538-28.284-7.343-43.479-6.78V0C12.314 1.045 24.388 2.071 36.62 3.401M215.232 52.852c-.625-.066-.897-.054-.931-.138-.116-.29-.148-.612-.212-.922 3.822-.01 7.646.047 11.467-.045 4.448-.108 10.797 1.341 12.842-.967 2.387-2.696 1.186-8.719 1.238-13.302.114-9.971-.109-19.948.109-29.915.08-3.655-.928-5.052-4.746-4.853-9.919.519-19.855.734-29.785 1.032-.975.03-1.958-.205-3.228-.58C214.559 1.945 227.423.991 240.73.003v52.882c-8.276 0-16.656 0-25.498-.033M220.071 177.398c-.271-.573-.08-1.561.129-1.565 4.993-.088 9.998-.259 14.98-.009 3.508.176 4.595-1.044 4.557-4.543-.165-15.149-.078-30.301-.088-45.911.458.372 1.32 1.202 1.324 2.036.079 16.424.059 32.848.059 50.036-6.984 0-13.737 0-20.961-.044M88.39 245.741c1.022.084 2.39.162 3.417.793 11.102 6.813 23.137 8.63 35.763 6.356 5.959-1.073 11.764-2.995 17.871-4.34-5.612 2.132-11.343 5.166-17.325 5.79-13.898 1.451-27.803 1.213-39.725-8.599M66.556 210.212c2.975 5.411 6.018 11.18 9.02 17.344-3.011-5.4-5.982-11.193-9.02-17.344M58.341 197.934c.666-.167 1.992-.36 2.238.068 2.107 3.662 4.018 7.437 5.985 11.557-2.69-3.535-5.376-7.449-8.223-11.625M44.637 185.241c5.099.438 8.268 3.862 10.737 8.564-3.518-2.513-7.043-5.393-10.737-8.564M82.29 48.422c-4.252-1.834-3.63-6.252-4.548-10.37 1.508 3.069 2.986 6.55 4.548 10.37M68.757 146.326c-2.533-2.157-5.166-4.637-7.882-7.455 2.539 2.151 5.16 4.641 7.882 7.455M75.532 228.226a122 122 0 0 1 6.047 9.319c-1.993-2.742-3.986-5.854-6.047-9.319M71.695 150.312c-.922-.799-1.921-1.91-2.906-3.366.952.788 1.89 1.921 2.906 3.366M46.663 130.007c-1.353-.103-2.966-.42-4.767-1.051 1.377.07 2.941.454 4.767 1.051M51.647 132.369a13.1 13.1 0 0 1-4.501-1.77c1.48.227 2.902.852 4.5 1.77M60.724 138.321a17.7 17.7 0 0 1-4.395-2.781c1.501.609 2.869 1.551 4.395 2.781M76.889 35.31c-.674-.781-1.402-1.898-2.068-3.39.711.768 1.362 1.912 2.068 3.39M85.668 243.124c.782.31 1.67.899 2.423 1.825-.862-.29-1.59-.919-2.423-1.825M41.596 128.372c-1.033.176-2.25.024-3.68-.432 1.023-.169 2.26-.032 3.68.432M55.367 194.37c.568.222 1.24.715 1.808 1.548-.637-.199-1.17-.738-1.808-1.548M82.397 239.295c.56.251 1.204.778 1.71 1.628-.633-.235-1.13-.794-1.71-1.628M84.291 241.679c.334.034.809.287 1.298.875-.376.004-.766-.326-1.298-.875M53.64 133.426c-.438.094-1.03-.081-1.764-.543.441-.101 1.026.086 1.764.543M77.805 37.409c-.322-.188-.666-.69-.978-1.52.34.182.648.693.978 1.52M81.588 238.108c.218-.039.507.19.823.707-.233.045-.493-.196-.823-.707M55.906 134.832c-.198.157-.537.145-.92-.161.262-.29.537-.195.92.161M57.276 196.552c.254-.079.578.009.756.361-.377.171-.557.018-.756-.361M54.888 134.25c-.215.129-.563.017-1.038-.351.217-.133.56-.012 1.038.351M64.014 16.858a.545.545 0 0 1-.71-.247c.344-.19.51-.067.71.247M65.027 17.882a.574.574 0 0 1-.734-.272c.35-.193.525-.063.734.272M67.045 19.939c-.244.093-.57.021-.777-.329.358-.192.55-.044.777.329\"></path></svg></div>\n<div class=\"skillTracker\" tabindex=\"0\" aria-label=\"Skill path: 0 of 8 skills earned\"><div class=\"skillTracker__ring\" style=\"--skillTracker-pct:0%\"><span class=\"skillTracker__ringLabel\">0<!-- -->/<!-- -->8</span></div><div class=\"skillTracker__panel\"><div class=\"skillTracker__header\"><span class=\"skillTracker__title\">🎯 Skill path</span><span class=\"skillTracker__count\">0<!-- -->/<!-- -->8<!-- --> earned</span></div><div class=\"skillTracker__series\">AI evals &amp; observability</div><ul class=\"skillTracker__steps\"><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">1</span><span class=\"skillTracker__skill\" data-state=\"locked\">Prove a model works</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">2</span><span class=\"skillTracker__skill\" data-state=\"locked\">Trustworthy JSON</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/synthetic-eval-datasets-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">3</span><span class=\"skillTracker__skill\" data-state=\"locked\">Real test data at scale</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/\"><span class=\"skillTracker__dot\" data-state=\"locked\">4</span><span class=\"skillTracker__skill\" data-state=\"locked\">Observe &amp; score production</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">5</span><span class=\"skillTracker__skill\" data-state=\"locked\">RAG, end to end</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/deepeval-regression-testing-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">6</span><span class=\"skillTracker__skill\" data-state=\"locked\">Catch regressions in CI</span></a></li><li><span class=\"skillTracker__step\"><span class=\"skillTracker__dot\" data-state=\"current\">7</span><span class=\"skillTracker__skill\" data-state=\"current\">Trace &amp; debug agent tool calls</span></span></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/web-search-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">🏆</span><span class=\"skillTracker__skill\" data-state=\"locked\">Add live web search</span></a></li></ul><div class=\"skillTracker__footer\">Finish this tutorial, then mark it complete below to earn the skill.</div></div></div>\n<p>Un agente que llama a herramientas hace dos cosas muy diferentes: <strong>razona</strong> (\"Debería buscar en la documentación\") y <strong>actúa</strong> (realmente llama a la herramienta). Cuando algo sale mal, la primera pregunta siempre es <em>qué capa falló</em> — ¿pensó mal o se rompió la acción? Un registro plano no puede responder eso. Un <strong>trazado</strong> puede.</p>\n<p>En este tutorial, construirás un pequeño agente que llama a herramientas en <strong>WEC Inference</strong>, lo instrumentarás con <strong>Langfuse</strong> para que cada paso de razonamiento y cada llamada a la herramienta se conviertan en un nodo inspeccionable, y luego depurarás <strong>dos</strong> fallos reales del árbol de trazado — incluyendo el peor tipo: una respuesta incorrecta y confiada que nunca lanza un error. Cada comando, error y captura de pantalla a continuación proviene de una ejecución real.</p>\n<!-- -->\n<div class=\"theme-admonition theme-admonition-info admonition_xJq3 alert alert--info\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z\"></path></svg></span>Reproducibilidad</div><div class=\"admonitionContent_BuS1\"><p>Construido en una instancia de WEC con Docker, contra <strong>WEC Inference</strong> (<code>Qwen3.5-122B</code>) y un <strong>Langfuse</strong> autoalojado del\n<a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/\">tutorial de observabilidad</a>. Las herramientas del agente incluyen el servicio RAG del\n<a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/\">tutorial de RAG</a> — así que esta pieza une toda la serie. Todas las llamadas al modelo permanecen en WEC; nada sale hacia una API de terceros.</p></div></div>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"lo-que-construirás\">Lo que construirás<a href=\"https://wec.wiline.com/docs/es/tutorials/component-tracing-agent-tool-calls-wiline/#lo-que-construir%C3%A1s\" class=\"hash-link\" aria-label=\"Enlace directo al Lo que construirás\" title=\"Enlace directo al Lo que construirás\" translate=\"no\">​</a></h2>\n<!-- -->\n<p>Las líneas punteadas son el punto: cada paso se informa a Langfuse, por lo que el <strong>razonamiento</strong> del agente y sus <strong>acciones</strong> se presentan como nodos separados e inspeccionables.</p>\n<p><strong>Requisitos previos:</strong> una instancia de WEC con Docker, una <strong>clave API de WEC Inference</strong>, un Langfuse en funcionamiento (clave pública + clave secreta, URL del host) y una herramienta que el agente pueda llamar — aquí el servicio RAG <code>/ask</code>. Todos los comandos se ejecutan en <code>~/agent-tracing</code>.</p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-1--ver-las-dos-capas-sin-trazado-aún\">Paso 1 — Ver las dos capas (sin trazado aún)<a href=\"https://wec.wiline.com/docs/es/tutorials/component-tracing-agent-tool-calls-wiline/#paso-1--ver-las-dos-capas-sin-trazado-a%C3%BAn\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 1 — Ver las dos capas (sin trazado aún)\" title=\"Enlace directo al Paso 1 — Ver las dos capas (sin trazado aún)\" translate=\"no\">​</a></h2>\n<p><strong>Regla: nunca instrumentes antes de haber visto el comportamiento en bruto.</strong> Así que v1 hace lo mínimo — pregunta al modelo una cuestión <em>con una definición de herramienta</em>, y muestra lo que regresa. Configura el espacio de trabajo y la clave de WEC:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">mkdir</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-p</span><span class=\"token plain\"> ~/agent-tracing </span><span class=\"token operator\" style=\"color:#393A34\">&amp;&amp;</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">cd</span><span class=\"token plain\"> ~/agent-tracing</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">grep</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'^WEC_API_KEY='</span><span class=\"token plain\"> ~/evolution-api/.env </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> .env</span><br></div></code></pre></div></div>\n<p><code>agent.py</code> (v1):</p>\n<div class=\"language-python codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">~/agent-tracing/agent.py (v1)</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-python codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> os</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> requests</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">WEC_URL </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"https://inference.wiline.com/v1/chat/completions\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">WEC_API_KEY </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> os</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">environ</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"WEC_API_KEY\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">MODEL </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Qwen3.5-122B\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">TOOLS </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token string\" style=\"color:#e3116c\">\"type\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"function\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token string\" style=\"color:#e3116c\">\"function\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"name\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"search_docs\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"description\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Buscar en la documentación de WEC una respuesta a una pregunta.\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"parameters\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string\" style=\"color:#e3116c\">\"type\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"object\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string\" style=\"color:#e3116c\">\"properties\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"query\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"type\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"string\"</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string\" style=\"color:#e3116c\">\"required\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"query\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">call_model</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">messages</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    r </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> requests</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">post</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">WEC_URL</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        headers</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"Authorization\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">f\"Bearer </span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">WEC_API_KEY</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">\"</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        json</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"model\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> MODEL</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"messages\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> messages</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"tools\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> TOOLS</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"tool_choice\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"auto\"</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        timeout</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token number\" style=\"color:#36acaa\">120</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    r</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">raise_for_status</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">return</span><span class=\"token plain\"> r</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">json</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"choices\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token number\" style=\"color:#36acaa\">0</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"message\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">if</span><span class=\"token plain\"> __name__ </span><span class=\"token operator\" style=\"color:#393A34\">==</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"__main__\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    msg </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> call_model</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"role\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"user\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"content\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"¿Cómo creo una instancia de cómputo en WEC?\"</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">print</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"=== CAPA DE RAZONAMIENTO (lo que pensó) ===\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">print</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">msg</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"reasoning_content\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:#00009f\">or</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"(ninguno)\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">print</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"\\n=== CAPA DE ACCIÓN (lo que decidió hacer) ===\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">for</span><span class=\"token plain\"> tc </span><span class=\"token keyword\" style=\"color:#00009f\">in</span><span class=\"token plain\"> msg</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"tool_calls\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:#00009f\">or</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:#00009f\">print</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">f\"herramienta: </span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">tc</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">[</span><span class=\"token string-interpolation interpolation string\" style=\"color:#e3116c\">'function'</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">]</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">[</span><span class=\"token string-interpolation interpolation string\" style=\"color:#e3116c\">'name'</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">]</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">  args: </span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">tc</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">[</span><span class=\"token string-interpolation interpolation string\" style=\"color:#e3116c\">'function'</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">]</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">[</span><span class=\"token string-interpolation interpolation string\" style=\"color:#e3116c\">'arguments'</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">]</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><br></div></code></pre></div></div>\n<p>Ejecuta en un contenedor (mismo patrón que el resto de la serie):</p>\n<div class=\"language-docker codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">~/agent-tracing/Dockerfile</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-docker codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">FROM python:3.12-slim</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">WORKDIR /app</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">RUN pip install --no-cache-dir requests</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">COPY agent.py .</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">CMD [\"python\", \"agent.py\"]</span><br></div></code></pre></div></div>\n<div class=\"language-yaml codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">~/agent-tracing/docker-compose.yml</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-yaml codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token key atrule\" style=\"color:#00a4db\">services</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">agent</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">build</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> .</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">env_file</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> .env</span><br></div></code></pre></div></div>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose run </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rm</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--build</span><span class=\"token plain\"> agent</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El razonamiento del modelo y su elección de herramienta impresos como dos bloques separados\" src=\"https://wec.wiline.com/docs/es/assets/images/agent-reasoning-action-56b6f9e7c5b8aef031cd06c04187e291.png\" width=\"862\" height=\"630\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 1.</strong> WEC Inference devuelve las dos capas <em>en una respuesta</em>: <code>reasoning_content</code>\n(el pensamiento) y <code>tool_calls</code> (la decisión). No tenemos que inferir la división — la API nos la entrega.</p>\n<div class=\"theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z\"></path></svg></span>WEC Inference soporta llamadas a herramientas — y expone el razonamiento</div><div class=\"admonitionContent_BuS1\"><p><code>Qwen3.5-122B</code> devuelve <code>finish_reason: tool_calls</code> con un arreglo <code>tool_calls</code> adecuado, <strong>más un campo <code>reasoning_content</code></strong> que contiene la cadena de pensamiento del modelo antes de actuar. Ese campo de razonamiento es exactamente lo que hace visible la separación entre razonamiento y acción más adelante.</p></div></div>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-2--cerrar-el-bucle-ejecutar-la-herramienta-obtener-la-respuesta\">Paso 2 — Cerrar el bucle: ejecutar la herramienta, obtener la respuesta<a href=\"https://wec.wiline.com/docs/es/tutorials/component-tracing-agent-tool-calls-wiline/#paso-2--cerrar-el-bucle-ejecutar-la-herramienta-obtener-la-respuesta\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 2 — Cerrar el bucle: ejecutar la herramienta, obtener la respuesta\" title=\"Enlace directo al Paso 2 — Cerrar el bucle: ejecutar la herramienta, obtener la respuesta\" translate=\"no\">​</a></h2>\n<p>v1 <em>decidió</em> buscar pero no lo hizo. Ahora llama realmente a la herramienta (el servicio RAG <code>/ask</code>), alimenta el resultado de vuelta y deja que el modelo escriba la respuesta final. Agrega la herramienta + el bucle:</p>\n<div class=\"language-python codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">~/agent-tracing/agent.py (v2, adiciones)</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-python codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> json</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">RAG_URL </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"http://&lt;tu-ip-vm&gt;:8000/ask\"</span><span class=\"token plain\">   </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># el servicio RAG = nuestra única herramienta</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">search_docs</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">query</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    r </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> requests</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">post</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">RAG_URL</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> json</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"question\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> query</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> timeout</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token number\" style=\"color:#36acaa\">120</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    r</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">raise_for_status</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">return</span><span class=\"token plain\"> r</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">json</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"answer\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">run</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">user_input</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    messages </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"role\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"user\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"content\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> user_input</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">while</span><span class=\"token plain\"> </span><span class=\"token boolean\" style=\"color:#36acaa\">True</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        msg </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> call_model</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">messages</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:#00009f\">if</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:#00009f\">not</span><span class=\"token plain\"> msg</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"tool_calls\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token keyword\" style=\"color:#00009f\">print</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"\\n=== RESPUESTA FINAL ===\\n\"</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">+</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">msg</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"content\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:#00009f\">or</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token keyword\" style=\"color:#00009f\">return</span><span class=\"token plain\"> msg</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"content\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        messages</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">append</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"role\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"assistant\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"content\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> msg</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"content\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:#00009f\">or</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">                         </span><span class=\"token string\" style=\"color:#e3116c\">\"tool_calls\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> msg</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"tool_calls\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:#00009f\">for</span><span class=\"token plain\"> tc </span><span class=\"token keyword\" style=\"color:#00009f\">in</span><span class=\"token plain\"> msg</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"tool_calls\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            args </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> json</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">loads</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">tc</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"function\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"arguments\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token keyword\" style=\"color:#00009f\">print</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">f\"[razonamiento] </span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">(</span><span class=\"token string-interpolation interpolation\">msg</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">.</span><span class=\"token string-interpolation interpolation\">get</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">(</span><span class=\"token string-interpolation interpolation string\" style=\"color:#e3116c\">'reasoning_content'</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">)</span><span class=\"token string-interpolation interpolation\"> </span><span class=\"token string-interpolation interpolation keyword\" style=\"color:#00009f\">or</span><span class=\"token string-interpolation interpolation\"> </span><span class=\"token string-interpolation interpolation string\" style=\"color:#e3116c\">''</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">)</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">.</span><span class=\"token string-interpolation interpolation\">strip</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">(</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">)</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">[</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">:</span><span class=\"token string-interpolation interpolation format-spec\">140]</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token keyword\" style=\"color:#00009f\">print</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">f\"[acción]    </span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">tc</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">[</span><span class=\"token string-interpolation interpolation string\" style=\"color:#e3116c\">'function'</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">]</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">[</span><span class=\"token string-interpolation interpolation string\" style=\"color:#e3116c\">'name'</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">]</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">(</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">args</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">)\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            result </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> search_docs</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">args</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"query\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token keyword\" style=\"color:#00009f\">print</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">f\"[resultado de la herramienta] </span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">result</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">[</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">:</span><span class=\"token string-interpolation interpolation format-spec\">140]</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">...\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            messages</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">append</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"role\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"tool\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"tool_call_id\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> tc</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"id\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"content\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> result</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><br></div></code></pre></div></div>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose run </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rm</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--build</span><span class=\"token plain\"> agent</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El bucle completo del agente: razonamiento, acción, resultado de la herramienta, luego la respuesta final fundamentada\" src=\"https://wec.wiline.com/docs/es/assets/images/agent-loop-b09953660869437421c9222da3f9b135.png\" width=\"862\" height=\"783\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 2.</strong> El bucle completo — el modelo razona, llama a <code>search_docs</code>, obtiene el resultado de RAG y escribe una respuesta fundamentada. Funciona. Pero un terminal que funciona no te dice <em>nada</em> sobre si el razonamiento fue sólido — eso es lo que arreglamos a continuación.</p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-3--agregar-langfuse-las-capas-se-convierten-en-un-árbol-de-trazado\">Paso 3 — Agregar Langfuse: las capas se convierten en un árbol de trazado<a href=\"https://wec.wiline.com/docs/es/tutorials/component-tracing-agent-tool-calls-wiline/#paso-3--agregar-langfuse-las-capas-se-convierten-en-un-%C3%A1rbol-de-trazado\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 3 — Agregar Langfuse: las capas se convierten en un árbol de trazado\" title=\"Enlace directo al Paso 3 — Agregar Langfuse: las capas se convierten en un árbol de trazado\" translate=\"no\">​</a></h2>\n<p>El terminal aplana todo en un solo flujo. Langfuse convierte cada función en un <strong>nodo</strong> para que puedas ver la estructura. Reutiliza las claves de Langfuse del tutorial de observabilidad (mismo proyecto que tus trazas de RAG):</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">grep</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-E</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'^LANGFUSE_(PUBLIC_KEY|SECRET_KEY|HOST)='</span><span class=\"token plain\"> ~/rag-service/.env </span><span class=\"token operator\" style=\"color:#393A34\">&gt;&gt;</span><span class=\"token plain\"> .env</span><br></div></code></pre></div></div>\n<p>Agrega el SDK (<code>pip install ... langfuse</code>) y decora tres funciones — esa es toda la instrumentación. <code>@observe</code> envuelve una función como un span; <code>as_type=\"generation\"</code> marca las llamadas LLM para que Langfuse capture modelo, tokens y costo:</p>\n<div class=\"language-python codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">~/agent-tracing/agent.py (v3, decoradores)</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-python codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token keyword\" style=\"color:#00009f\">from</span><span class=\"token plain\"> langfuse </span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> observe</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> get_client</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token decorator annotation punctuation\" style=\"color:#393A34\">@observe</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">as_type</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token string\" style=\"color:#e3116c\">\"generation\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">call_model</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">messages</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    data </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> r</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">json</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    get_client</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">update_current_generation</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">model</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\">MODEL</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> usage_details</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\">data</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"usage\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">return</span><span class=\"token plain\"> data</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"choices\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token number\" style=\"color:#36acaa\">0</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"message\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token decorator annotation punctuation\" style=\"color:#393A34\">@observe</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">search_docs</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">query</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token decorator annotation punctuation\" style=\"color:#393A34\">@observe</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">run</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">user_input</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">if</span><span class=\"token plain\"> __name__ </span><span class=\"token operator\" style=\"color:#393A34\">==</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"__main__\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    run</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"¿Cómo creo una instancia de cómputo en WEC?\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    get_client</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">flush</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\">   </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># script de corta duración: empujar trazas antes de salir</span><br></div></code></pre></div></div>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose run </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rm</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--build</span><span class=\"token plain\"> agent</span><br></div></code></pre></div></div>\n<p>Abre Langfuse → <strong>Trazado → Trazas</strong>. Lo primero que notarás: hay <strong>dos</strong> trazas por pregunta.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"La lista de trazas de Langfuse mostrando una traza de ejecución y una traza de pregunta\" src=\"https://wec.wiline.com/docs/es/assets/images/trace-list-3a0d985919db7d12160c4109ab85dede.png\" width=\"1901\" height=\"763\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 3.</strong> Trazado distribuido, gratis: la traza <code>run</code> de tu agente y la propia traza <code>ask</code> del servicio RAG son <em>separadas</em> — cada servicio se instrumenta a sí mismo. Ver ambas es cómo sigues una solicitud a través de los límites del servicio.</p>\n<p>Abre la más nueva <code>run</code>:</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El árbol de trazado de Langfuse: ejecución, dos generaciones de call_model y el span de search_docs\" src=\"https://wec.wiline.com/docs/es/assets/images/trace-tree-green-c19c35d6b1ba42189355bdd0e79d96f8.png\" width=\"1902\" height=\"996\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 4.</strong> El turno del agente como un árbol: <code>run</code> → <code>call_model</code> (decidir) → <code>search_docs</code> (actuar) → <code>call_model</code> (respuesta). Langfuse también capturó <strong>latencia</strong>, <strong>uso de tokens</strong> (801→306) y una <strong>puntuación</strong> de corrección — todo por tres decoradores.</p>\n<p>Haz clic en el primer <code>call_model</code> y abre su salida:</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El nodo call_model mostrando reasoning_content y tool_calls en Langfuse\" src=\"https://wec.wiline.com/docs/es/assets/images/trace-reasoning-content-c5e9876f6e9a640d45e9f5f54c5594cc.png\" width=\"1899\" height=\"996\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 5.</strong> La capa de razonamiento, capturada: <code>reasoning_content</code> (\"<em>Debería buscar en la documentación de WEC…</em>\") se encuentra justo al lado de los <code>tool_calls</code> que produjo. Este es el registro contra el que depuras.</p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-4--romperlo-de-la-manera-ruidosa-luego-depurar-desde-el-trazado\">Paso 4 — Romperlo (de la manera ruidosa), luego depurar desde el trazado<a href=\"https://wec.wiline.com/docs/es/tutorials/component-tracing-agent-tool-calls-wiline/#paso-4--romperlo-de-la-manera-ruidosa-luego-depurar-desde-el-trazado\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 4 — Romperlo (de la manera ruidosa), luego depurar desde el trazado\" title=\"Enlace directo al Paso 4 — Romperlo (de la manera ruidosa), luego depurar desde el trazado\" translate=\"no\">​</a></h2>\n<p>Planta un <strong>error realista</strong>: la API de RAG espera <code>{\"question\": ...}</code>, pero es natural enviar <code>{\"query\": ...}</code> porque el parámetro de la <em>herramienta</em> se llama <code>query</code>. Una clave incorrecta:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">sed</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-i</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'s/{\"question\": query}/{\"query\": query}/'</span><span class=\"token plain\"> agent.py</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose run </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rm</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--build</span><span class=\"token plain\"> agent</span><br></div></code></pre></div></div>\n<p>Se bloquea:</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El terminal mostrando un rastreo de error HTTP 422\" src=\"https://wec.wiline.com/docs/es/assets/images/agent-422-crash-af97e2fe3ae5709ec3fe6d5f859095a0.png\" width=\"859\" height=\"862\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 6.</strong> El terminal te da un <strong>rastreo de pila</strong> — te dice <em>dónde se rompió el código</em>. No te dice <strong>si el agente pensó correctamente</strong>. Para eso, ve al trazado.</p>\n<p>Abre la <code>run</code> fallida y haz clic en el span rojo <code>search_docs</code>:</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El trazado roto: call_model verde, search_docs rojo con un error 422\" src=\"https://wec.wiline.com/docs/es/assets/images/trace-error-span-ddca621bf15a8cc64c96371cd6a42e1a.png\" width=\"1901\" height=\"997\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 7.</strong> Todo el diagnóstico en una imagen: <code>call_model</code> es <strong>verde</strong> (el razonamiento fue correcto — eligió correctamente buscar), <code>search_docs</code> es <strong>rojo</strong>, murió en <strong>0.04s</strong>, y el panel muestra el <code>422</code> más la carga útil exacta que envió. Un fallo instantáneo es un <em>rechazo</em>, no un tiempo de espera — apuntando directamente a una mala solicitud.</p>\n<p>Arregla la línea y confirma el verde:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">sed</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-i</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'s/{\"query\": query}/{\"question\": query}/'</span><span class=\"token plain\"> agent.py</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose run </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rm</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--build</span><span class=\"token plain\"> agent</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El trazado corregido: todos los nodos verdes, respuesta final producida\" src=\"https://wec.wiline.com/docs/es/assets/images/trace-fixed-green-c3b4677676dc40f861fca6dafe1594bb.png\" width=\"1896\" height=\"997\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 8.</strong> De vuelta al verde — <code>search_docs</code> tiene éxito y se produce la respuesta final. Diagnosticaste <em>y</em> verificaste la corrección desde el trazado.</p>\n<p>Ese fue el fallo <strong>ruidoso</strong> — se bloqueó, así que lo habrías atrapado eventualmente incluso sin un trazado. El peligroso es el siguiente.</p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-5--una-segunda-herramienta-para-que-el-agente-tenga-que-elegir\">Paso 5 — Una segunda herramienta, para que el agente tenga que <em>elegir</em><a href=\"https://wec.wiline.com/docs/es/tutorials/component-tracing-agent-tool-calls-wiline/#paso-5--una-segunda-herramienta-para-que-el-agente-tenga-que-elegir\" class=\"hash-link\" aria-label=\"Enlace directo al paso-5--una-segunda-herramienta-para-que-el-agente-tenga-que-elegir\" title=\"Enlace directo al paso-5--una-segunda-herramienta-para-que-el-agente-tenga-que-elegir\" translate=\"no\">​</a></h2>\n<p>Los agentes reales tienen más de una herramienta, y las decisiones interesantes ocurren cuando el modelo elige entre ellas. Agrega una herramienta <code>get_pricing</code> junto a <code>search_docs</code>, y enruta las llamadas a través de una <strong>tabla de despacho</strong> indexada por el nombre de la herramienta:</p>\n<div class=\"language-python codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">~/agent-tracing/agent.py (v4, dos herramientas + despacho)</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-python codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">TOOLS </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"type\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"function\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"function\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token string\" style=\"color:#e3116c\">\"name\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"search_docs\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token string\" style=\"color:#e3116c\">\"description\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Buscar en la documentación de WEC preguntas sobre cómo hacer y configurar.\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token string\" style=\"color:#e3116c\">\"parameters\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"type\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"object\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"properties\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"query\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"type\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"string\"</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"required\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"query\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"type\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"function\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"function\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token string\" style=\"color:#e3116c\">\"name\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"get_pricing\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token string\" style=\"color:#e3116c\">\"description\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Obtener el precio de un recurso de WEC (por ejemplo, 'instancia de cómputo', 'almacenamiento en bloque', 'inferencia').\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token string\" style=\"color:#e3116c\">\"parameters\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"type\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"object\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"properties\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"resource\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"type\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"string\"</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"required\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"resource\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">PRICES </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token string\" style=\"color:#e3116c\">\"instancia de cómputo\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"$0.03 / hora-vCPU\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token string\" style=\"color:#e3116c\">\"almacenamiento en bloque\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"$0.10 / GB-mes\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token string\" style=\"color:#e3116c\">\"inferencia\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"$0.50 por 1M tokens\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token decorator annotation punctuation\" style=\"color:#393A34\">@observe</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">get_pricing</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">resource</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">return</span><span class=\"token plain\"> PRICES</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">resource</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">lower</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">strip</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">f\"No se encontró precio para '</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">resource</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">'.\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">TOOL_FUNCS </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"search_docs\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> search_docs</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"get_pricing\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> get_pricing</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># en run(), despachar por la herramienta que el modelo realmente eligió:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">for</span><span class=\"token plain\"> tc </span><span class=\"token keyword\" style=\"color:#00009f\">in</span><span class=\"token plain\"> msg</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"tool_calls\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    name </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> tc</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"function\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"name\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    args </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> json</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">loads</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">tc</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"function\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"arguments\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">print</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">f\"[acción] </span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">name</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">(</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">args</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">)\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    result </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token builtin\">str</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">TOOL_FUNCS</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token plain\">name</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token operator\" style=\"color:#393A34\">**</span><span class=\"token plain\">args</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    messages</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">append</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"role\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"tool\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"tool_call_id\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> tc</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"id\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"content\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> result</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><br></div></code></pre></div></div>\n<p>Haz una pregunta de <strong>precio</strong> (<code>run(\"¿Cuánto cuesta una instancia de cómputo en WEC?\")</code>) y ejecútala. El agente debería elegir <code>get_pricing</code>, no <code>search_docs</code>:</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Traza de Langfuse donde el agente eligió correctamente get_pricing\" src=\"https://wec.wiline.com/docs/es/assets/images/trace-two-tools-ba891f9c0ec4d99aea2e499dcbba3189.png\" width=\"1899\" height=\"995\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 9.</strong> Selección genuina de herramientas: dado dos herramientas, el agente eligió <code>get_pricing</code> y respondió <strong>$0.03/hora-vCPU</strong>, Corrección 1.00. La elección en sí misma es ahora algo que puedes ver en el trazado.</p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-6--el-fallo-silencioso-por-qué-el-trazado-se-gana-su-lugar\">Paso 6 — El fallo silencioso (por qué el trazado se gana su lugar)<a href=\"https://wec.wiline.com/docs/es/tutorials/component-tracing-agent-tool-calls-wiline/#paso-6--el-fallo-silencioso-por-qu%C3%A9-el-trazado-se-gana-su-lugar\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 6 — El fallo silencioso (por qué el trazado se gana su lugar)\" title=\"Enlace directo al Paso 6 — El fallo silencioso (por qué el trazado se gana su lugar)\" translate=\"no\">​</a></h2>\n<p>Ahora un error que <strong>no se bloquea</strong>. Alguien refactoriza el despacho y codifica en duro la antigua herramienta única, olvidando la nueva — así que cada llamada ejecuta <code>search_docs</code> sin importar lo que eligió el modelo:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">sed</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-i</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'s/result = str(TOOL_FUNCS\\[name\\](\\*\\*args))/result = str(search_docs(list(args.values())[0]))  # ERROR/'</span><span class=\"token plain\"> agent.py</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose run </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rm</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--build</span><span class=\"token plain\"> agent</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Una ejecución exitosa — con una respuesta completamente incorrecta</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">[action] get_pricing({'resource': 'instancia de cómputo'})</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">=== RESPUESTA FINAL ===</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Basado en la información de precios disponible, la categoría de instancia de cómputo más cara en WEC es $1,586.77. ...</span><br></div></code></pre></div></div>\n<p>Lee eso cuidadosamente. El modelo <strong>eligió <code>get_pricing</code></strong> (correcto). La respuesta es confiada, detallada — y <strong>completamente incorrecta</strong> (el precio real es $0.03/hora-vCPU). No hay <strong>error, ni rastreo</strong>. Un registro se vería perfectamente saludable. Este es el modo de fallo que se envía a producción y miente silenciosamente a los usuarios.</p>\n<p>El trazado es lo único que lo atrapa. Abre la <code>run</code>:</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El trazado de fallo silencioso: get_pricing elegido pero search_docs ejecutado, Corrección 0.00\" src=\"https://wec.wiline.com/docs/es/assets/images/trace-silent-failure-162a3ce9394bad7caca0e908be683b67.png\" width=\"1901\" height=\"996\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 10.</strong> Tres indicios que un registro no puede mostrar: (1) el span ejecutado es <strong><code>search_docs</code></strong> — no hay <strong>span de <code>get_pricing</code></strong>, a pesar de que el modelo lo eligió: <em>decisión ≠ acción</em>; (2) el nodo de razonamiento puntuó <strong>Corrección 0.00</strong> — el juez LLM marcó automáticamente la respuesta incorrecta; (3) la salida muestra al modelo <em>confundido</em> por un resultado de herramienta que no coincidía con lo que pidió. Arregla el despacho de vuelta a <code>TOOL_FUNCS[name](**args)</code> y el precio es correcto nuevamente.</p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Habilidad desbloqueada 🏅</div><div class=\"admonitionContent_BuS1\"><p>Instrumentaste un agente que llama a herramientas para que su razonamiento y sus acciones sean nodos de trazado separados e inspeccionables — y depuraste <strong>dos</strong> fallos reales desde el trazado: el ruido del bloqueo <em>y</em> la respuesta silenciosa, confiada pero incorrecta que ningún rastreo de pila revelaría jamás.</p></div></div>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"solución-de-problemas\">Solución de problemas<a href=\"https://wec.wiline.com/docs/es/tutorials/component-tracing-agent-tool-calls-wiline/#soluci%C3%B3n-de-problemas\" class=\"hash-link\" aria-label=\"Enlace directo al Solución de problemas\" title=\"Enlace directo al Solución de problemas\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\"><strong>No aparece trazado.</strong> Un script corto sale antes de que el SDK se vacíe. Llama a\n<code>get_client().flush()</code> antes de que el proceso termine (ya está en v3).</li>\n<li class=\"\"><strong>Span de <code>search_docs</code> faltante.</strong> Solo las funciones decoradas con <code>@observe</code> se convierten en nodos —\ndecora la función, no el sitio de llamada.</li>\n<li class=\"\"><strong>La generación no muestra tokens/costo.</strong> Pasa <code>usage_details=data.get(\"usage\")</code> a través de\n<code>update_current_generation</code>; sin ello, Langfuse no puede calcular el costo.</li>\n<li class=\"\"><strong>Las trazas del agente y de la herramienta parecen desconectadas.</strong> Esperado — cada servicio se traza a sí mismo. Conéctalos más tarde con propagación de trazas si necesitas una vista única entre servicios.</li>\n<li class=\"\"><strong>Una respuesta incorrecta silenciosa sin error.</strong> Compara los <code>tool_calls</code> del modelo (lo que eligió)\ncontra los spans ejecutados (lo que realmente se ejecutó). Un desajuste es un error de despacho/ruteo — y una puntuación de corrección en el trazado lo señalará automáticamente.</li>\n</ul>\n<hr>\n<div class=\"skillComplete\"><span class=\"skillComplete__medal\" aria-hidden=\"true\">🎯</span><div class=\"skillComplete__text\"><div class=\"skillComplete__title\">Finished this tutorial?</div><div class=\"skillComplete__sub\">Mark it complete to earn <strong>Trace &amp; debug agent tool calls</strong> on your skill path.</div></div><button type=\"button\" class=\"skillComplete__btn\">Mark as complete ✓</button></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"qué-sigue\">¿Qué sigue?<a href=\"https://wec.wiline.com/docs/es/tutorials/component-tracing-agent-tool-calls-wiline/#qu%C3%A9-sigue\" class=\"hash-link\" aria-label=\"Enlace directo al ¿Qué sigue?\" title=\"Enlace directo al ¿Qué sigue?\" translate=\"no\">​</a></h2>\n<p>Ahora puedes ver a un agente pensar, actuar y <em>fallar silenciosamente</em> — y atraparlo. Apunta la misma instrumentación a un agente real de múltiples pasos, agrega puntuación de corrección automatizada en cada trazado, y tendrás una observabilidad de producción que revela fallos silenciosos antes de que lo hagan tus usuarios.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"desmontaje\">Desmontaje<a href=\"https://wec.wiline.com/docs/es/tutorials/component-tracing-agent-tool-calls-wiline/#desmontaje\" class=\"hash-link\" aria-label=\"Enlace directo al Desmontaje\" title=\"Enlace directo al Desmontaje\" translate=\"no\">​</a></h2>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token builtin class-name\">cd</span><span class=\"token plain\"> ~/agent-tracing </span><span class=\"token operator\" style=\"color:#393A34\">&amp;&amp;</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose down </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rmi</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">local</span><br></div></code></pre></div></div>",
            "url": "https://wec.wiline.com/docs/es/tutorials/component-tracing-agent-tool-calls-wiline/",
            "title": "Trazado a nivel de componente: depuración de llamadas a herramientas del agente",
            "summary": "El pensamiento de tu agente y sus acciones son dos capas diferentes. Construye un agente que llame a herramientas en WEC Inference, trázalo con Langfuse y luego depura dos fallos reales del trazado — incluyendo la respuesta incorrecta y confiada que nunca lanza un rastreo de pila.",
            "date_modified": "2026-07-22T00:00:00.000Z",
            "author": {
                "name": "Rafael Fernandes",
                "url": "https://www.linkedin.com/in/rafaelmacariofernandes/"
            },
            "tags": [
                "ai",
                "evals",
                "observabilidad",
                "langfuse",
                "agents",
                "tracing",
                "inference"
            ]
        },
        {
            "id": "https://wec.wiline.com/docs/es/tutorials/whatsapp-ai-assistant-evolution-api/",
            "content_html": "<div class=\"tutorialHero\"><img class=\"tutorialHero__brand\" src=\"https://wec.wiline.com/docs/es/assets/images/whatsapp_logo-cc34546fcff90d36128c7230c48c496f.png\" alt=\"WhatsApp\"><span class=\"tutorialHero__plus\">+</span><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"242\" height=\"255\" fill=\"none\" viewBox=\"0 0 242 255\" class=\"tutorialHero__wiline\"><path fill=\"#4E8EF7\" d=\"M56.711 11.293A195 195 0 0 1 41.899 4.79c4.788 1.85 9.71 4.028 14.812 6.504M73.807 29.35c-1.302-1.446-2.666-3.233-4.02-5.38 1.326 1.44 2.642 3.24 4.02 5.38M69.761 23.354c-.832-.585-1.752-1.472-2.558-2.714.9.567 1.685 1.49 2.558 2.714M62.97 15.774a10.7 10.7 0 0 1-3.158-1.841c.993.299 1.986.956 3.159 1.841M59.715 13.346c-.818-.194-1.782-.674-2.829-1.494.84.175 1.761.691 2.83 1.494M39.317 3.782c-.524.192-1.413.302-2.455.14.594-.255 1.342-.238 2.455-.14M74.906 31.339c-.334-.153-.73-.61-1.117-1.398.358.143.706.619 1.117 1.398M66.024 18.878c-.239.1-.557.05-.752-.276.35-.201.532-.069.752.276M71.773 150.625c3.363 7.291 7.185 14.411 9.983 21.914 4.474 11.997 11.167 20.832 24.296 24.757 10.62 3.175 20.369 1.795 30.093-1.816 9.937-3.689 15.165-11.978 18.501-21.541 4.349-12.466 9.385-24.443 19.668-33.446 11.32-9.909 24.639-14.788 39.429-15.63 8.3-.473 16.649-.081 25.444.444.472 15.673.385 30.825.55 45.974.038 3.499-1.049 4.719-4.557 4.543-4.982-.25-9.987-.079-14.98.009-.209.004-.4.991-.6 1.521-23.106 1.695-37.691 15.22-47.961 34.651-3.387 6.408-5.913 13.38-10.013 19.273-4.368 6.277-10.134 11.582-15.853 17.196-6.439 1.421-12.244 3.342-18.202 4.416-12.627 2.274-24.661.457-35.764-6.356-1.028-.631-2.395-.709-3.604-1.043 0 0-.117-.11-.047-.495-.818-.973-1.706-1.562-2.593-2.15 0 0 .09-.044.05-.335-.513-.545-.988-.798-1.463-1.052 0 0-.07-.123.012-.492-.561-.894-1.205-1.421-1.848-1.947 0 0 .134-.068.102-.28-.321-.442-.61-.671-.898-.901 0 0 .086-.007.074-.336-2.05-3.429-4.089-6.529-6.127-9.629 0 0 .094-.017.123-.365-3.013-6.117-6.056-11.886-9.1-17.655 0 0 .096-.01.088-.342-1.978-4.073-3.89-7.848-5.997-11.51-.246-.428-1.572-.235-2.398-.329 0 0-.226-.202-.114-.613-.213-.499-.537-.587-.861-.674 0 0-.032-.101.02-.455-.62-.846-1.291-1.339-1.964-1.832 0 0 .133-.083.119-.372-2.476-4.624-5.645-8.048-10.914-8.777 0 0 .108-.031.01-.326-1.673-1.444-3.105-2.918-4.85-3.688-9.751-4.308-20.013-5.643-30.63-5.23-7.305.285-7.316.051-7.317-7.446q-.002-18.496-.002-36.992v-3.57h36.106s-.08.019.133.283c1.43.415 2.646.567 3.863.719 0 0-.077.022.113.296 1.804.591 3.417.908 5.03 1.225 0 0 .139-.037.152.369 1.592.962 3.17 1.517 4.748 2.073 0 0-.091.027.052.267.735.415 1.328.59 1.92.765 0 0-.076.033.053.246.477.325.825.438 1.173.551 0 0-.149.018-.07.246.418.239.757.251 1.096.263 0 0 .23.058.189.465 1.522 1.319 3.084 2.231 4.647 3.143 0 0-.105.033-.015.316 2.723 2.764 5.356 5.244 7.989 7.724 0 0-.104.062-.094.352 1.01 1.401 2.01 2.512 3.01 3.624\"></path><path fill=\"#4E8EF7\" d=\"M214.77 52.819c-3.343.846-6.723 1.574-10.024 2.562-11.388 3.408-20.567 10.142-27.166 19.865-5.097 7.511-9.621 15.483-13.685 23.608-6.809 13.613-15.725 24.809-31.343 28.198-24.707 5.362-45.22-.126-57.852-24.564C69.466 92.363 63.8 82.375 57.349 73c-3.369-4.896-8.866-8.326-13.679-13.041-1.11-1.409-1.81-2.472-2.794-2.937-10.697-5.056-21.87-7.3-33.686-5.344-3.877.641-5.72-.297-5.615-4.86.294-12.816.26-25.646.019-38.464C1.509 3.874 3.15 2.5 7.429 2.716c9.774.492 19.564.678 29.347.989 0 0-.076.026.082.257 1.046.122 1.935.012 2.825-.098.575.043 1.15.086 2.038.692 5.265 2.72 10.218 4.877 15.17 7.034 0 0-.108.03-.016.317 1.058.765 2.022 1.245 2.987 1.724 0 0-.113.048-.082.354 1.144.876 2.258 1.446 3.372 2.017 0 0 .18.145.097.506.223.404.53.447.837.49 0 0 .214.13.144.504.244.426.558.477.873.528 0 0 .171.124.108.485.256.412.574.464.893.515 0 0 .167.127.105.493.265.437.592.509.919.58 0 0 .078.108.019.5.861 1.28 1.782 2.166 2.702 3.053 0 0-.101.061-.087.368 1.379 2.093 2.743 3.88 4.107 5.666 0 0-.094.011-.092.297.4.742.795 1.2 1.19 1.656 0 0-.121.02-.165.337.685 1.432 1.413 2.549 2.14 3.665 0 0-.11.024-.129.299.326.776.67 1.277 1.014 1.78 0 0-.084-.005-.1.367.933 4.079.311 8.497 4.648 10.67 4.69 12.164 12.107 21.794 25.852 23.497 19.618 2.431 35.321.062 45.299-24.554 3.398-8.383 7.099-16.902 12.232-24.264 8.353-11.979 21.046-17.795 35.832-20.32 1.666.417 2.649.651 3.624.622 9.93-.298 19.865-.513 29.785-1.032 3.818-.2 4.825 1.198 4.746 4.853-.218 9.967.004 19.944-.109 29.915-.052 4.583 1.149 10.606-1.239 13.302-2.044 2.308-8.394.86-12.842.967-3.82.092-7.644.036-11.467.045.065.31.097.633.213.922.034.084.306.072.469.105M37.585 127.354c-11.585.344-23.368.344-35.907.344v3.57q0 18.496.002 36.992c0 7.497.012 7.731 7.318 7.446 10.616-.413 20.878.922 30.63 5.23 1.743.77 3.176 2.244 4.84 3.744-13.655-7.873-28.93-7.767-44.468-7.398v-52.503c12.516.308 25.063-1.222 37.585 2.575\"></path><path fill=\"#4E8EF7\" d=\"M36.619 3.401c-9.626-.007-19.415-.193-29.19-.685C3.151 2.5 1.51 3.874 1.594 8.354c.241 12.818.275 25.648-.018 38.464-.105 4.563 1.737 5.501 5.614 4.86 11.816-1.956 22.99.288 33.686 5.344.984.465 1.685 1.529 2.616 2.66-13.39-7.538-28.284-7.343-43.479-6.78V0C12.314 1.045 24.388 2.071 36.62 3.401M215.232 52.852c-.625-.066-.897-.054-.931-.138-.116-.29-.148-.612-.212-.922 3.822-.01 7.646.047 11.467-.045 4.448-.108 10.797 1.341 12.842-.967 2.387-2.696 1.186-8.719 1.238-13.302.114-9.971-.109-19.948.109-29.915.08-3.655-.928-5.052-4.746-4.853-9.919.519-19.855.734-29.785 1.032-.975.03-1.958-.205-3.228-.58C214.559 1.945 227.423.991 240.73.003v52.882c-8.276 0-16.656 0-25.498-.033M220.071 177.398c-.271-.573-.08-1.561.129-1.565 4.993-.088 9.998-.259 14.98-.009 3.508.176 4.595-1.044 4.557-4.543-.165-15.149-.078-30.301-.088-45.911.458.372 1.32 1.202 1.324 2.036.079 16.424.059 32.848.059 50.036-6.984 0-13.737 0-20.961-.044M88.39 245.741c1.022.084 2.39.162 3.417.793 11.102 6.813 23.137 8.63 35.763 6.356 5.959-1.073 11.764-2.995 17.871-4.34-5.612 2.132-11.343 5.166-17.325 5.79-13.898 1.451-27.803 1.213-39.725-8.599M66.556 210.212c2.975 5.411 6.018 11.18 9.02 17.344-3.011-5.4-5.982-11.193-9.02-17.344M58.341 197.934c.666-.167 1.992-.36 2.238.068 2.107 3.662 4.018 7.437 5.985 11.557-2.69-3.535-5.376-7.449-8.223-11.625M44.637 185.241c5.099.438 8.268 3.862 10.737 8.564-3.518-2.513-7.043-5.393-10.737-8.564M82.29 48.422c-4.252-1.834-3.63-6.252-4.548-10.37 1.508 3.069 2.986 6.55 4.548 10.37M68.757 146.326c-2.533-2.157-5.166-4.637-7.882-7.455 2.539 2.151 5.16 4.641 7.882 7.455M75.532 228.226a122 122 0 0 1 6.047 9.319c-1.993-2.742-3.986-5.854-6.047-9.319M71.695 150.312c-.922-.799-1.921-1.91-2.906-3.366.952.788 1.89 1.921 2.906 3.366M46.663 130.007c-1.353-.103-2.966-.42-4.767-1.051 1.377.07 2.941.454 4.767 1.051M51.647 132.369a13.1 13.1 0 0 1-4.501-1.77c1.48.227 2.902.852 4.5 1.77M60.724 138.321a17.7 17.7 0 0 1-4.395-2.781c1.501.609 2.869 1.551 4.395 2.781M76.889 35.31c-.674-.781-1.402-1.898-2.068-3.39.711.768 1.362 1.912 2.068 3.39M85.668 243.124c.782.31 1.67.899 2.423 1.825-.862-.29-1.59-.919-2.423-1.825M41.596 128.372c-1.033.176-2.25.024-3.68-.432 1.023-.169 2.26-.032 3.68.432M55.367 194.37c.568.222 1.24.715 1.808 1.548-.637-.199-1.17-.738-1.808-1.548M82.397 239.295c.56.251 1.204.778 1.71 1.628-.633-.235-1.13-.794-1.71-1.628M84.291 241.679c.334.034.809.287 1.298.875-.376.004-.766-.326-1.298-.875M53.64 133.426c-.438.094-1.03-.081-1.764-.543.441-.101 1.026.086 1.764.543M77.805 37.409c-.322-.188-.666-.69-.978-1.52.34.182.648.693.978 1.52M81.588 238.108c.218-.039.507.19.823.707-.233.045-.493-.196-.823-.707M55.906 134.832c-.198.157-.537.145-.92-.161.262-.29.537-.195.92.161M57.276 196.552c.254-.079.578.009.756.361-.377.171-.557.018-.756-.361M54.888 134.25c-.215.129-.563.017-1.038-.351.217-.133.56-.012 1.038.351M64.014 16.858a.545.545 0 0 1-.71-.247c.344-.19.51-.067.71.247M65.027 17.882a.574.574 0 0 1-.734-.272c.35-.193.525-.063.734.272M67.045 19.939c-.244.093-.57.021-.777-.329.358-.192.55-.044.777.329\"></path></svg><span class=\"tutorialHero__plus\">+</span><img class=\"tutorialHero__evolution\" src=\"https://wec.wiline.com/docs/es/assets/images/0_hT2ECga7Z67dEbYz-62f9ab60d339ae8868fee46475e505bc.png\" alt=\"Evolution\"></div>\n<div class=\"skillTracker\" tabindex=\"0\" aria-label=\"Skill path: 0 of 2 skills earned\"><div class=\"skillTracker__ring\" style=\"--skillTracker-pct:0%\"><span class=\"skillTracker__ringLabel\">0<!-- -->/<!-- -->2</span></div><div class=\"skillTracker__panel\"><div class=\"skillTracker__header\"><span class=\"skillTracker__title\">🎯 Skill path</span><span class=\"skillTracker__count\">0<!-- -->/<!-- -->2<!-- --> earned</span></div><div class=\"skillTracker__series\">WhatsApp automation on WEC</div><ul class=\"skillTracker__steps\"><li><span class=\"skillTracker__step\"><span class=\"skillTracker__dot\" data-state=\"current\">1</span><span class=\"skillTracker__skill\" data-state=\"current\">Self-host a WhatsApp AI bridge</span></span></li><li><span class=\"skillTracker__step\"><span class=\"skillTracker__dot\" data-state=\"locked\">🏆</span><span class=\"skillTracker__skill\" data-state=\"locked\">Production delivery via Cloud API</span></span></li></ul><div class=\"skillTracker__footer\">Finish this tutorial, then mark it complete below to earn the skill.</div></div></div>\n<p>La mayoría de las guías de \"autoalbergar un asistente de IA para WhatsApp\" se detienen en \"el contenedor se inició\". Esta va hasta el final: despliegas una puerta de enlace de WhatsApp programable real (<strong>API de Evolution</strong>), luego <strong>escribes el puente tú mismo</strong> — las ~50 líneas que convierten un mensaje entrante en una respuesta de LLM y la envían de vuelta. Ese puente (webhook → modelo → respuesta) es el patrón reutilizable detrás de <em>cada</em> integración de chat-AI: SMS, Slack, Telegram, voz — cambia el canal, la forma es idéntica.</p>\n<p>Y porque esto es una construcción real, encontramos — y solucionamos — cada detalle: una imagen que movió a los editores, un bucle de versión de Baileys, un bucle de respuesta infinito, spam en grupos de chat, la nueva dirección <strong>LID</strong> de WhatsApp, y una genuina <strong>pared de entrega</strong> que la mayoría de los tutoriales pretenden que no existe. Cada comando, error y salida a continuación es de una ejecución real.</p>\n<!-- -->\n<div class=\"theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z\"></path></svg></span>Lee esto antes de usar tu número personal</div><div class=\"admonitionContent_BuS1\"><p>Evolution se vincula a un número de WhatsApp como un <strong>dispositivo compañero</strong> (como WhatsApp Web) — el bot actúa <em>como esa cuenta</em> y puede leer cada DM y grupo que recibe. Para una demostración en un número que controlas está bien; para cualquier cosa real, usa un <strong>número dedicado</strong>. Y consulta la <a href=\"https://wec.wiline.com/docs/es/tutorials/whatsapp-ai-assistant-evolution-api/#the-delivery-wall\" class=\"\">limitación de entrega</a> al final antes de construir esto en producción.</p></div></div>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"lo-que-construirás\">Lo que construirás<a href=\"https://wec.wiline.com/docs/es/tutorials/whatsapp-ai-assistant-evolution-api/#lo-que-construir%C3%A1s\" class=\"hash-link\" aria-label=\"Enlace directo al Lo que construirás\" title=\"Enlace directo al Lo que construirás\" translate=\"no\">​</a></h2>\n<!-- -->\n<p>Cuatro contenedores: Evolution + su Postgres + Redis, y el <strong>puente</strong> que escribirás. El puente es el objetivo principal — todo lo demás es estándar.</p>\n<p><strong>Requisitos previos:</strong> una instancia WEC con Docker + Compose, una <strong>clave de API de Inferencia WEC</strong> (<a class=\"\" href=\"https://wec.wiline.com/docs/es/cloud_portal/platform/inference/api_keys/\">Inferencia → Claves de API</a>), un número de WhatsApp para la demostración, y ~2 GB de espacio libre en disco.</p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-1--desplegar-la-pila-de-evolution\">Paso 1 — Desplegar la pila de Evolution<a href=\"https://wec.wiline.com/docs/es/tutorials/whatsapp-ai-assistant-evolution-api/#paso-1--desplegar-la-pila-de-evolution\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 1 — Desplegar la pila de Evolution\" title=\"Enlace directo al Paso 1 — Desplegar la pila de Evolution\" translate=\"no\">​</a></h2>\n<p>Evolution necesita su propio Postgres y Redis. Le damos un conjunto dedicado en una red interna y exponemos solo el puerto de la API (8080). <code>docker-compose.yml</code>:</p>\n<div class=\"language-yaml codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">~/evolution-api/docker-compose.yml</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-yaml codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token key atrule\" style=\"color:#00a4db\">services</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">evolution-api</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">image</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> evoapicloud/evolution</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">api</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\">v2.2.3</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">restart</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> unless</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">stopped</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">ports</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"8080:8080\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">env_file</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> .env</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">volumes</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\"> evolution_instances</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\">/evolution/instances</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">depends_on</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token plain\">evolution</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">postgres</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> evolution</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">redis</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">logging</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">driver</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> json</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">file</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">options</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"> </span><span class=\"token key atrule\" style=\"color:#00a4db\">max-size</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"50m\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token key atrule\" style=\"color:#00a4db\">max-file</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"3\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">evolution-postgres</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">image</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> postgres</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token number\" style=\"color:#36acaa\">17</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">restart</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> unless</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">stopped</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">environment</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">POSTGRES_USER</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> evolution</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">POSTGRES_PASSWORD</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> $</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\">POSTGRES_PASSWORD</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">POSTGRES_DB</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> evolution</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">volumes</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\"> evolution_pgdata</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\">/var/lib/postgresql/data</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">evolution-redis</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">image</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> redis</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token number\" style=\"color:#36acaa\">7</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">restart</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> unless</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">stopped</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">volumes</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\"> evolution_redis</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\">/data</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token key atrule\" style=\"color:#00a4db\">volumes</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">evolution_instances</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">evolution_pgdata</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  evolution_redis</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><br></div></code></pre></div></div>\n<div class=\"theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z\"></path></svg></span>Rotación de logs desde el primer día</div><div class=\"admonitionContent_BuS1\"><p>El bloque <code>logging:</code> limita los logs del contenedor a 3×50 MB. Si lo omites, un contenedor ruidoso puede llenar el disco y hacer que la máquina se caiga — una lección aprendida de la manera difícil en esta VM. Configúralo antes de necesitarlo.</p></div></div>\n<p>El archivo <code>.env</code> (secretos generados, URIs de conexión y — lo más importante — una <strong>versión de WhatsApp-Web de Baileys fijada</strong>):</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Generar el .env</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token builtin class-name\">cd</span><span class=\"token plain\"> ~/evolution-api </span><span class=\"token operator\" style=\"color:#393A34\">&amp;&amp;</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">cat</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> .env </span><span class=\"token operator\" style=\"color:#393A34\">&lt;&lt;</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">EOF</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">AUTHENTICATION_API_KEY=</span><span class=\"token string variable\" style=\"color:#36acaa\">$(</span><span class=\"token string variable\" style=\"color:#36acaa\">openssl rand </span><span class=\"token string variable parameter variable\" style=\"color:#36acaa\">-hex</span><span class=\"token string variable\" style=\"color:#36acaa\"> </span><span class=\"token string variable number\" style=\"color:#36acaa\">24</span><span class=\"token string variable\" style=\"color:#36acaa\">)</span><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">SERVER_URL=http://&lt;tu-ip-vm&gt;:8080</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">POSTGRES_PASSWORD=</span><span class=\"token string variable\" style=\"color:#36acaa\">$(</span><span class=\"token string variable\" style=\"color:#36acaa\">openssl rand </span><span class=\"token string variable parameter variable\" style=\"color:#36acaa\">-hex</span><span class=\"token string variable\" style=\"color:#36acaa\"> </span><span class=\"token string variable number\" style=\"color:#36acaa\">16</span><span class=\"token string variable\" style=\"color:#36acaa\">)</span><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">DATABASE_ENABLED=true</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">DATABASE_PROVIDER=postgresql</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">DATABASE_CONNECTION_URI=postgresql://evolution:\\</span><span class=\"token string variable\" style=\"color:#36acaa\">${POSTGRES_PASSWORD}</span><span class=\"token string\" style=\"color:#e3116c\">@evolution-postgres:5432/evolution?schema=public</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">CACHE_REDIS_ENABLED=true</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">CACHE_REDIS_URI=redis://evolution-redis:6379/0</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">CACHE_REDIS_PREFIX_KEY=evolution</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">CACHE_LOCAL_ENABLED=false</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">QRCODE_LIMIT=30</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">EOF</span><br></div></code></pre></div></div>\n<p>Levántalo:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose up </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">sleep</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">15</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-s</span><span class=\"token plain\"> http://localhost:8080 </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> jq</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">{</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  \"status\": 200,</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  \"message\": \"¡Bienvenido a la API de Evolution, está funcionando!\",</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  \"version\": \"2.2.3\",</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  ...</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">}</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"La API de Evolution responde en el puerto 8080 con la carga de bienvenida\" src=\"https://wec.wiline.com/docs/es/assets/images/wa-evolution-up-680a7f5fe9e4a450cacf33f1df5ed6f7.png\" width=\"628\" height=\"182\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 1.</strong> La pila está activa: Evolution responde en <code>:8080</code> con su versión y estado.</p>\n<div class=\"theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z\"></path></svg></span>Dos problemas reales al extraer la imagen</div><div class=\"admonitionContent_BuS1\"><p><strong>(1) El editor se mudó.</strong> La imagen a la que la mayoría de las guías hacen referencia — <code>atendai/evolution-api</code> — ahora devuelve <em>\"acceso denegado al pull / el repositorio no existe.\"</em> La imagen actual es <strong><code>evoapicloud/evolution-api</code></strong>. La lista de etiquetas del antiguo repositorio aún se resuelve, lo que te envía por un camino sin salida; confirma con un pull fresco de <code>hello-world</code> que no está limitando la tasa.\n<strong>(2) Fija una etiqueta real.</strong> <code>v2.1.1</code> (de un blog popular) nunca fue publicada — verifica <code>https://hub.docker.com/v2/repositories/evoapicloud/evolution-api/tags</code> y fija una que exista (usamos <code>v2.2.3</code>).</p></div></div>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-2--crear-una-instancia-y-vincular-whatsapp\">Paso 2 — Crear una instancia y vincular WhatsApp<a href=\"https://wec.wiline.com/docs/es/tutorials/whatsapp-ai-assistant-evolution-api/#paso-2--crear-una-instancia-y-vincular-whatsapp\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 2 — Crear una instancia y vincular WhatsApp\" title=\"Enlace directo al Paso 2 — Crear una instancia y vincular WhatsApp\" translate=\"no\">​</a></h2>\n<p>Establece tu clave de API como una variable de shell (cada solicitud la necesita en un encabezado <code>apikey:</code>):</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token assign-left variable\" style=\"color:#36acaa\">APIKEY</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token variable\" style=\"color:#36acaa\">$(</span><span class=\"token variable function\" style=\"color:#d73a49\">grep</span><span class=\"token variable\" style=\"color:#36acaa\"> </span><span class=\"token variable string\" style=\"color:#e3116c\">'^AUTHENTICATION_API_KEY='</span><span class=\"token variable\" style=\"color:#36acaa\"> .env </span><span class=\"token variable operator\" style=\"color:#393A34\">|</span><span class=\"token variable\" style=\"color:#36acaa\"> </span><span class=\"token variable function\" style=\"color:#d73a49\">cut</span><span class=\"token variable\" style=\"color:#36acaa\"> </span><span class=\"token variable parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token variable operator\" style=\"color:#393A34\">=</span><span class=\"token variable\" style=\"color:#36acaa\"> </span><span class=\"token variable parameter variable\" style=\"color:#36acaa\">-f2</span><span class=\"token variable\" style=\"color:#36acaa\">)</span><br></div></code></pre></div></div>\n<p>Crea la instancia:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-s</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-X</span><span class=\"token plain\"> POST http://localhost:8080/instance/create </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"apikey: </span><span class=\"token string variable\" style=\"color:#36acaa\">$APIKEY</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Content-Type: application/json\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'{\"instanceName\":\"wec-demo\",\"integration\":\"WHATSAPP-BAILEYS\",\"qrcode\":true}'</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> jq </span><span class=\"token string\" style=\"color:#e3116c\">'.instance'</span><br></div></code></pre></div></div>\n<p>Luego abre el Administrador integrado en <code>http://&lt;tu-ip-vm&gt;:8080/manager</code>, ingresa tu URL de servidor + clave de API, haz clic en <strong>wec-demo</strong>, y escanea el QR desde <strong>WhatsApp → Configuración → Dispositivos vinculados → Vincular un dispositivo</strong>. Confirma que está conectado:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-s</span><span class=\"token plain\"> http://localhost:8080/instance/fetchInstances </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"apikey: </span><span class=\"token string variable\" style=\"color:#36acaa\">$APIKEY</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> jq </span><span class=\"token string\" style=\"color:#e3116c\">'.[] | {name, connectionStatus}'</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">{ \"name\": \"wec-demo\", \"connectionStatus\": \"open\" }</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El Administrador de Evolution muestra la instancia wec-demo conectada\" src=\"https://wec.wiline.com/docs/es/assets/images/wa-manager-connected-d91fda82b98c6238d0d747b47636645b.png\" width=\"2834\" height=\"1468\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 2.</strong> El panel de control del Administrador de Evolution una vez que la instancia se vincula: <code>Conectado</code>, con conteos de contactos, chats y mensajes en vivo.</p>\n<div class=\"theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z\"></path></svg></span>El bucle de versión de Baileys — el fallo número 1 de autoalbergado</div><div class=\"admonitionContent_BuS1\"><p>Si la instancia nunca sale de <code>conectando</code> y los logs muestran <code>ChannelStartupService</code> reinicializándose cada pocos segundos con una línea <code>Baileys version env: 2,3000,...</code>, la versión de WhatsApp-Web fijada está <strong>obsoleta</strong> y WhatsApp rechaza el apretón de manos antes de que se genere un QR. Obtén la versión actual y fíjala, luego recrea:</p><div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-s</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"https://raw.githubusercontent.com/WhiskeySockets/Baileys/master/src/Defaults/baileys-version.json\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># {\"version\":[2,3000,1035194821]}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token builtin class-name\">echo</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"CONFIG_SESSION_PHONE_VERSION=2.3000.1035194821\"</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;&gt;</span><span class=\"token plain\"> .env</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose up </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> --force-recreate evolution-api</span><br></div></code></pre></div></div></div></div>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-3--el-puente-v1-ver-el-webhook\">Paso 3 — El puente, v1: <em>ver</em> el webhook<a href=\"https://wec.wiline.com/docs/es/tutorials/whatsapp-ai-assistant-evolution-api/#paso-3--el-puente-v1-ver-el-webhook\" class=\"hash-link\" aria-label=\"Enlace directo al paso-3--el-puente-v1-ver-el-webhook\" title=\"Enlace directo al paso-3--el-puente-v1-ver-el-webhook\" translate=\"no\">​</a></h2>\n<p>Ahora la parte que realmente escribes. <strong>Regla: nunca conectes la lógica antes de haber visto los datos reales.</strong> Así que v1 no hace nada más que imprimir lo que Evolution envía.</p>\n<p>El puente se ejecuta como otro contenedor en el mismo Compose, por lo que Evolution lo alcanza <strong>por nombre</strong> (<code>http://bridge:8090</code>) y el puente alcanza a Evolution en <code>http://evolution-api:8080</code>. Crea <code>bridge/main.py</code>:</p>\n<div class=\"language-python codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">~/evolution-api/bridge/main.py (v1)</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-python codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token keyword\" style=\"color:#00009f\">from</span><span class=\"token plain\"> fastapi </span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> FastAPI</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> Request</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">app </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> FastAPI</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token decorator annotation punctuation\" style=\"color:#393A34\">@app</span><span class=\"token decorator annotation punctuation\" style=\"color:#393A34\">.</span><span class=\"token decorator annotation punctuation\" style=\"color:#393A34\">post</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"/webhook\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">async</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">webhook</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">request</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> Request</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    data </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:#00009f\">await</span><span class=\"token plain\"> request</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">json</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">print</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"=== WEBHOOK RECIBIDO ===\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> flush</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token boolean\" style=\"color:#36acaa\">True</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">print</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">data</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> flush</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token boolean\" style=\"color:#36acaa\">True</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">return</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"received\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token boolean\" style=\"color:#36acaa\">True</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><br></div></code></pre></div></div>\n<div class=\"theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z\"></path></svg></span><code>flush=True</code> es importante</div><div class=\"admonitionContent_BuS1\"><p>Sin él, la salida de <code>print</code> se almacena en búfer y nunca aparece en <code>docker compose logs</code> — estarás mirando un log vacío convencido de que está roto.</p></div></div>\n<p><code>bridge/Dockerfile</code>:</p>\n<div class=\"language-docker codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">~/evolution-api/bridge/Dockerfile</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-docker codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">FROM python:3.12-slim</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">WORKDIR /app</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">RUN pip install --no-cache-dir fastapi uvicorn requests</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">COPY main.py .</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">CMD [\"uvicorn\", \"main:app\", \"--host\", \"0.0.0.0\", \"--port\", \"8090\"]</span><br></div></code></pre></div></div>\n<p>Agrega el servicio a <code>docker-compose.yml</code> (dentro de <code>services:</code>), luego constrúyelo:</p>\n<div class=\"language-yaml codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">docker-compose.yml (agregar bajo services:)</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-yaml codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">bridge</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">build</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> ./bridge</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">restart</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> unless</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">stopped</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">env_file</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> .env</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">depends_on</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token plain\">evolution</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">api</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">logging</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">driver</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> json</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">file</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">options</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"> </span><span class=\"token key atrule\" style=\"color:#00a4db\">max-size</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"10m\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token key atrule\" style=\"color:#00a4db\">max-file</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"3\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><br></div></code></pre></div></div>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose up </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--build</span><span class=\"token plain\"> bridge</span><br></div></code></pre></div></div>\n<p>Ahora <strong>registra el webhook</strong> para que Evolution envíe mensajes al puente:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-s</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-X</span><span class=\"token plain\"> POST </span><span class=\"token string\" style=\"color:#e3116c\">\"http://localhost:8080/webhook/set/wec-demo\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"apikey: </span><span class=\"token string variable\" style=\"color:#36acaa\">$APIKEY</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Content-Type: application/json\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'{\"webhook\":{\"enabled\":true,\"url\":\"http://bridge:8090/webhook\",\"webhookByEvents\":false,\"events\":[\"MESSAGES_UPSERT\"]}}'</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> jq </span><span class=\"token string\" style=\"color:#e3116c\">'.enabled, .url'</span><br></div></code></pre></div></div>\n<p>Sigue los logs (<code>docker compose logs -f bridge</code>) y envíate un mensaje de WhatsApp. La carga real aparece:</p>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Un webhook MESSAGES_UPSERT real</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">{'event': 'messages.upsert', 'instance': 'wec-demo',</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"> 'data': {'key': {'remoteJid': '123456789012345@lid', 'fromMe': False, 'id': '...'},</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">          'pushName': 'Contacto de Prueba',</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">          'message': {'conversation': 'Oii'},</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">          'messageType': 'conversation'},</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"> 'sender': '5511988887777@s.whatsapp.net', ...}</span><br></div></code></pre></div></div>\n<p>Tres cosas que ahora <em>sabemos</em> — y cada una impulsa el código siguiente:</p>\n<ul>\n<li class=\"\"><strong>text</strong> está en <code>data.message.conversation</code> (plano) o <code>data.message.extendedTextMessage.text</code> (citado)</li>\n<li class=\"\"><strong><code>data.key.fromMe</code></strong> — <code>True</code> para nuestros propios mensajes (el guardia de bucle)</li>\n<li class=\"\"><strong><code>remoteJid</code> termina en <code>@lid</code></strong> — el identificador de privacidad de WhatsApp, <em>no</em> un número de teléfono (esta es una saga; Paso 5)</li>\n</ul>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-4--v2-leer-pensar-responder-con-el-guardia-de-bucle\">Paso 4 — v2: leer, pensar, responder (con el guardia de bucle)<a href=\"https://wec.wiline.com/docs/es/tutorials/whatsapp-ai-assistant-evolution-api/#paso-4--v2-leer-pensar-responder-con-el-guardia-de-bucle\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 4 — v2: leer, pensar, responder (con el guardia de bucle)\" title=\"Enlace directo al Paso 4 — v2: leer, pensar, responder (con el guardia de bucle)\" translate=\"no\">​</a></h2>\n<p>Dale al puente la clave de Inferencia WEC para que pueda llamar al modelo:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token builtin class-name\">echo</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"WEC_API_KEY=&lt;tu-clave-de-inferencia-wec&gt;\"</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;&gt;</span><span class=\"token plain\"> ~/evolution-api/.env</span><br></div></code></pre></div></div>\n<p>Ahora la lógica — <code>main.py</code> v2:</p>\n<div class=\"language-python codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">~/evolution-api/bridge/main.py (v2)</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-python codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> os</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> requests</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">from</span><span class=\"token plain\"> fastapi </span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> FastAPI</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> Request</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">app </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> FastAPI</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">EVOLUTION_URL </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"http://evolution-api:8080\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">INSTANCE </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"wec-demo\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">EVOLUTION_APIKEY </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> os</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">environ</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"AUTHENTICATION_API_KEY\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">WEC_API_KEY </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> os</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">environ</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"WEC_API_KEY\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">WEC_URL </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"https://inference.wiline.com/v1/chat/completions\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">MODEL </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Qwen2.5-3B-Instruct\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">ask_llm</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">text</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token builtin\">str</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> </span><span class=\"token builtin\">str</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    r </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> requests</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">post</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">WEC_URL</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        headers</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"Authorization\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">f\"Bearer </span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">WEC_API_KEY</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">\"</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        json</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"model\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> MODEL</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"messages\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"role\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"user\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"content\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> text</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        timeout</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token number\" style=\"color:#36acaa\">60</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    r</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">raise_for_status</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">return</span><span class=\"token plain\"> r</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">json</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"choices\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token number\" style=\"color:#36acaa\">0</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"message\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"content\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">send_whatsapp</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">number</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token builtin\">str</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> text</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token builtin\">str</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    r </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> requests</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">post</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">f\"</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">EVOLUTION_URL</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">/message/sendText/</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">INSTANCE</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        headers</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"apikey\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> EVOLUTION_APIKEY</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Content-Type\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"application/json\"</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        json</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"number\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> number</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"text\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> text</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> timeout</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token number\" style=\"color:#36acaa\">60</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">print</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">f\"ENVIAR -&gt; </span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">r</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">.</span><span class=\"token string-interpolation interpolation\">status_code</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> flush</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token boolean\" style=\"color:#36acaa\">True</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token decorator annotation punctuation\" style=\"color:#393A34\">@app</span><span class=\"token decorator annotation punctuation\" style=\"color:#393A34\">.</span><span class=\"token decorator annotation punctuation\" style=\"color:#393A34\">post</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"/webhook\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">async</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">webhook</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">request</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> Request</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    data </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token keyword\" style=\"color:#00009f\">await</span><span class=\"token plain\"> request</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">json</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"data\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    key </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> data</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"key\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">if</span><span class=\"token plain\"> key</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"fromMe\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\">                     </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># (1) GUARDIA DE BUCLE</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:#00009f\">return</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"skipped\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"fromMe\"</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    msg </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> data</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"message\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\">             </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># (2) extraer texto</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    text </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> msg</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"conversation\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:#00009f\">or</span><span class=\"token plain\"> msg</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"extendedTextMessage\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"text\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">if</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:#00009f\">not</span><span class=\"token plain\"> text</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:#00009f\">return</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"skipped\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"no text\"</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    to </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> key</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"remoteJid\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">print</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">f\"ENTRADA </span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">to</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">: </span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">text</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> flush</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token boolean\" style=\"color:#36acaa\">True</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    answer </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> ask_llm</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">text</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\">                     </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># (3) pensar</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    send_whatsapp</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">to</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> answer</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\">                  </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># (4) responder</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">print</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">f\"SALIDA </span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">to</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">: </span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">answer</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">[</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">:</span><span class=\"token string-interpolation interpolation format-spec\">60]</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">...\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> flush</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token boolean\" style=\"color:#36acaa\">True</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">return</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"ok\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token boolean\" style=\"color:#36acaa\">True</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><br></div></code></pre></div></div>\n<p>La única línea que los principiantes siempre pasan por alto es <strong>(1) el guardia de bucle</strong>. Cuando el puente envía una respuesta, ese mensaje saliente dispara <em>otro</em> webhook <code>MESSAGES_UPSERT</code> con <code>fromMe: true</code> — sin el guardia, el puente se responde a sí mismo, para siempre. Reconstruye:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose up </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--build</span><span class=\"token plain\"> bridge</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El puente registrando ENTRADA y SALIDA para un DM en vivo\" src=\"https://wec.wiline.com/docs/es/assets/images/wa-bridge-logs-53cb3cf64a3d62d69794ee3a0bf3742b.png\" width=\"768\" height=\"205\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 3.</strong> v2 en acción: un DM llega, el modelo responde, la respuesta se envía.</p>\n<p>Ahora responde a DMs. Pero observa lo que sucede a continuación.</p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-5--las-dos-verdaderas-paredes-spam-en-grupos-luego-lid\">Paso 5 — Las dos verdaderas paredes: spam en grupos, luego LID<a href=\"https://wec.wiline.com/docs/es/tutorials/whatsapp-ai-assistant-evolution-api/#paso-5--las-dos-verdaderas-paredes-spam-en-grupos-luego-lid\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 5 — Las dos verdaderas paredes: spam en grupos, luego LID\" title=\"Enlace directo al Paso 5 — Las dos verdaderas paredes: spam en grupos, luego LID\" translate=\"no\">​</a></h2>\n<p>Sigue los logs con el bot en vivo y verás que intenta responder <strong>a cada grupo en el que estás</strong>, incluidos grupos de promoción/spam, y Evolution <strong>se agota</strong> al enviarles — errores de inundación:</p>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Los grupos inundan el puente</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">ENTRADA 120363040812472138@g.us: *ALÔ CORREDORES 🏃 ... pechin.co/135449*</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">requests.exceptions.ReadTimeout: HTTPConnectionPool(host='evolution-api', port=8080): Read timed out.</span><br></div></code></pre></div></div>\n<p>Una cuenta compañera recibe <strong>todo</strong>. Solución: responder <strong>solo a DMs</strong> — los grupos terminan en <code>@g.us</code>:</p>\n<div class=\"language-python codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-python codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    to </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> key</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"remoteJid\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">if</span><span class=\"token plain\"> to</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">endswith</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"@g.us\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:#00009f\">or</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"broadcast\"</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:#00009f\">in</span><span class=\"token plain\"> to</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\">   </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># Solo DMs</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:#00009f\">return</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"skipped\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"not a DM\"</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><br></div></code></pre></div></div>\n<p>Ahora la pared más sutil. Con los grupos filtrados, un DM real llega — pero la respuesta es <strong>rechazada</strong>:</p>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">ENVIAR -&gt; 400</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">{\"status\":400,\"error\":\"Bad Request\",\"response\":{\"message\":[</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  {\"exists\":false,\"jid\":\"123456789012345@lid\",\"name\":\"Contacto de Prueba\",\"number\":\"123456789012345@lid\"}]}}</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Evolution rechazando una respuesta a una dirección @lid con exists\" src=\"https://wec.wiline.com/docs/es/assets/images/wa-lid-400-84cc0430abac7bdd5aa6d29cf1c100b5.png\" width=\"863\" height=\"218\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 4.</strong> La pared LID en los logs: el DM entrante llega como <code>@lid</code>, y la respuesta es rechazada con <code>exists:false</code>.</p>\n<p><code>exists: false</code>. Esta es <strong>la dirección LID de WhatsApp</strong> (un cambio de privacidad de 2025): los DMs entrantes llegan con un identificador <code>@lid</code>, y <strong>no puedes enviar de vuelta a un <code>@lid</code></strong> — Evolution necesita el verdadero JID de teléfono <code>@s.whatsapp.net</code>. Sin embargo, el almacén de contactos de Evolution tiene ambos:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-s</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-X</span><span class=\"token plain\"> POST </span><span class=\"token string\" style=\"color:#e3116c\">\"http://localhost:8080/chat/findContacts/wec-demo\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"apikey: </span><span class=\"token string variable\" style=\"color:#36acaa\">$APIKEY</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Content-Type: application/json\"</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'{}'</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> jq </span><span class=\"token string\" style=\"color:#e3116c\">'[.[] | select(.pushName==\"Contacto de Prueba\")]'</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida — la misma persona, dos identidades</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">[ { \"remoteJid\": \"123456789012345@lid\",         \"pushName\": \"Contacto de Prueba\" },</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  { \"remoteJid\": \"5511999999999@s.whatsapp.net\", \"pushName\": \"Contacto de Prueba\" } ]</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"findContacts devolviendo el LID y el JID de teléfono para un contacto\" src=\"https://wec.wiline.com/docs/es/assets/images/wa-findcontacts-275c01bee94ec0da990f2cdd0ec6acf2.png\" width=\"862\" height=\"429\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 5.</strong> Evolution almacena ambas identidades para la misma persona — el <code>@lid</code> y el verdadero JID de teléfono. Esa es la clave para resolver la dirección de respuesta.</p>\n<p>Así que <strong>resolvemos el <code>@lid</code> al número de teléfono</strong> antes de responder. Agrega un resolvedor y úsalo:</p>\n<div class=\"language-python codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-python codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">resolve_number</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">remote_jid</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token builtin\">str</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> push_name</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token builtin\">str</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">if</span><span class=\"token plain\"> remote_jid</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">endswith</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"@s.whatsapp.net\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:#00009f\">return</span><span class=\"token plain\"> remote_jid</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">split</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"@\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token number\" style=\"color:#36acaa\">0</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># LID: encontrar el número real del contacto haciendo coincidir el nombre</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    contacts </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> requests</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">post</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">f\"</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">EVOLUTION_URL</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">/chat/findContacts/</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">INSTANCE</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        headers</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"apikey\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> EVOLUTION_APIKEY</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Content-Type\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"application/json\"</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        json</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> timeout</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token number\" style=\"color:#36acaa\">30</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">json</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">for</span><span class=\"token plain\"> c </span><span class=\"token keyword\" style=\"color:#00009f\">in</span><span class=\"token plain\"> contacts</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:#00009f\">if</span><span class=\"token plain\"> c</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"pushName\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">==</span><span class=\"token plain\"> push_name </span><span class=\"token keyword\" style=\"color:#00009f\">and</span><span class=\"token plain\"> c</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"remoteJid\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">endswith</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"@s.whatsapp.net\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token keyword\" style=\"color:#00009f\">return</span><span class=\"token plain\"> c</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"remoteJid\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">split</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"@\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token number\" style=\"color:#36acaa\">0</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">return</span><span class=\"token plain\"> </span><span class=\"token boolean\" style=\"color:#36acaa\">None</span><br></div></code></pre></div></div>\n<p>En el manejador, resuelve antes de enviar:</p>\n<div class=\"language-python codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-python codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    number </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> resolve_number</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">to</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> data</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"pushName\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">if</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:#00009f\">not</span><span class=\"token plain\"> number</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:#00009f\">return</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"skipped\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"unresolved lid\"</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    send_whatsapp</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">number</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> answer</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><br></div></code></pre></div></div>\n<p>Ahora <code>ENVIAR -&gt; 201</code>. (Hacer coincidir por nombre es un heurístico — dos contactos que comparten un nombre colisionarían; la producción mantiene un mapa adecuado LID→teléfono. Pero funciona, y es el estado honesto del soporte LID de Evolution en v2.2.3.)</p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-6--habla-whatsapp-no-markdown\">Paso 6 — Habla WhatsApp, no Markdown<a href=\"https://wec.wiline.com/docs/es/tutorials/whatsapp-ai-assistant-evolution-api/#paso-6--habla-whatsapp-no-markdown\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 6 — Habla WhatsApp, no Markdown\" title=\"Enlace directo al Paso 6 — Habla WhatsApp, no Markdown\" translate=\"no\">​</a></h2>\n<p>Los LLM emiten <strong>Markdown</strong> (<code>**negrita**</code>, <code>[texto](url)</code>), pero WhatsApp tiene su <em>propio</em> formato: la negrita es <code>*asterisco simple*</code>, y los enlaces de Markdown no se renderizan. Así que <code>**Compute**</code> aparece con asteriscos literales. Traduce la salida del modelo antes de enviar, y agrega las fuentes del RAG como URLs simples:</p>\n<div class=\"language-python codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-python codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> re</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">to_whatsapp</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">md</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token builtin\">str</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> </span><span class=\"token builtin\">str</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    md </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> re</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">sub</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">r\"\\*\\*(.+?)\\*\\*\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">r\"*\\1*\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> md</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\">                        </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># **negrita** -&gt; *negrita*</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    md </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> re</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">sub</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">r\"\\[([^\\]]+)\\]\\((https?://[^)]+)\\)\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">r\"\\1 (\\2)\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> md</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\">  </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># [t](url) -&gt; t (url)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    md </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> re</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">sub</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">r\"(?m)^#{1,6}\\s*\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> md</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\">                            </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># eliminar encabezados #</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">return</span><span class=\"token plain\"> md</span><br></div></code></pre></div></div>\n<p>Verificado contra una respuesta real — exactamente lo que WhatsApp renderizará:</p>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida formateada (determinista, no se necesita entrega)</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Para crear una instancia de computación en WiLine Edge Cloud (WEC), sigue estos pasos:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">1. Inicia sesión en WiLine Edge Cloud.</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">2. En la barra lateral, haz clic en *Compute*.</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">3. Selecciona *Instances*.</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">4. Haz clic en el asistente \"Launch Virtual Machine\" para comenzar el proceso de despliegue.</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">_Fuentes:_</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">https://wec.wiline.com/docs/cloud_portal/platform/compute/instances/compute_instance/</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"La respuesta formateada de Markdown a WhatsApp\" src=\"https://wec.wiline.com/docs/es/assets/images/wa-formatted-output-766cfd620ca72685d8f37110da283d56.png\" width=\"861\" height=\"444\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 6.</strong> La salida del modelo después de to_whatsapp(): negrita con asterisco simple, enlaces simples, fuentes añadidas.</p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"mejora--apunta-el-puente-a-tu-documentación-rag\">Mejora — apunta el puente a tu documentación (RAG)<a href=\"https://wec.wiline.com/docs/es/tutorials/whatsapp-ai-assistant-evolution-api/#mejora--apunta-el-puente-a-tu-documentaci%C3%B3n-rag\" class=\"hash-link\" aria-label=\"Enlace directo al Mejora — apunta el puente a tu documentación (RAG)\" title=\"Enlace directo al Mejora — apunta el puente a tu documentación (RAG)\" translate=\"no\">​</a></h2>\n<p>Todo hasta ahora usa el modelo en bruto, así que una pregunta de WEC obtiene una respuesta <em>general</em>. Para hacer que responda desde <strong>tu documentación</strong>, cambia <code>ask_llm</code> para llamar al servicio RAG de la <a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/\">serie de evals</a> — <strong>una función</strong>, nada más cambia:</p>\n<div class=\"language-python codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-python codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">RAG_URL </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"http://&lt;tu-ip-vm&gt;:8000/ask\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">ask_llm</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">text</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token builtin\">str</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> </span><span class=\"token builtin\">str</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    r </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> requests</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">post</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">RAG_URL</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> json</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"question\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> text</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> timeout</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token number\" style=\"color:#36acaa\">120</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    r</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">raise_for_status</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    d </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> r</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">json</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    answer </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> to_whatsapp</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">d</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"answer\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    sources </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> d</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"sources\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token number\" style=\"color:#36acaa\">2</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">if</span><span class=\"token plain\"> sources</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        answer </span><span class=\"token operator\" style=\"color:#393A34\">+=</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"\\n\\n_Fuentes:_\\n\"</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">+</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"\\n\"</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">join</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">sources</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">return</span><span class=\"token plain\"> answer</span><br></div></code></pre></div></div>\n<p>Ese es el beneficio del patrón de puente: <strong>cambia el cerebro, mantén la plomería.</strong> Ahora WhatsApp responde a preguntas de WEC desde la documentación real, con fuentes:</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"WhatsApp entregando una respuesta fundamentada en documentos de WEC\" src=\"https://wec.wiline.com/docs/es/assets/images/wa-bridge-reply-bf0eaa9a06721ee1396cbf44fd96ae3a.png\" width=\"1316\" height=\"1729\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 7.</strong> Un mensaje real en → el puente → RAG sobre la documentación de WEC → una respuesta fundamentada entregada en WhatsApp, fuentes incluidas.</p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Habilidad desbloqueada 🏅</div><div class=\"admonitionContent_BuS1\"><p>Has autoalbergado una puerta de enlace programable de WhatsApp y escribiste el puente que lo convierte en un asistente de IA — webhook entrante, modelo saliente, respuesta de vuelta — fundamentado en tu propia documentación.</p></div></div>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"la-pared-de-entrega\">La pared de entrega<a href=\"https://wec.wiline.com/docs/es/tutorials/whatsapp-ai-assistant-evolution-api/#la-pared-de-entrega\" class=\"hash-link\" aria-label=\"Enlace directo al La pared de entrega\" title=\"Enlace directo al La pared de entrega\" translate=\"no\">​</a></h2>\n<p>Aquí está la parte que otros tutoriales no te dirán. Incluso con <code>ENVIAR -&gt; 201</code>, las respuestas a una <strong>cuenta migrada a LID</strong> frecuentemente llegan como <strong>\"Esperando este mensaje\"</strong> en el teléfono del destinatario — WhatsApp aceptó el texto cifrado pero ningún dispositivo puede descifrarlo. Es una limitación conocida del <strong>dispositivo compañero de Baileys</strong>, y el despliegue de LID de WhatsApp lo empeora.</p>\n<p>Lo que observamos, honestamente:</p>\n<ul>\n<li class=\"\">Un <strong>nuevo re-vinculo</strong> (cierra sesión en el dispositivo, escanea un nuevo QR) compra una <strong>ventana corta</strong> donde la entrega funciona sin problemas.</li>\n<li class=\"\">Después de un tiempo, la sesión se degrada y <code>\"Esperando este mensaje\"</code> regresa — aunque cada envío aún informa <code>201</code>.</li>\n</ul>\n<p>Así que el puente es correcto de extremo a extremo; el <em>transporte no oficial de Baileys</em> es el eslabón débil. Si estás construyendo esto para algo real:</p>\n<ul>\n<li class=\"\"><strong>Usa un número dedicado, no LID</strong> — el problema está vinculado a cuentas migradas a LID.</li>\n<li class=\"\">Para una fiabilidad de grado de producción, usa la <strong>API oficial de WhatsApp Cloud</strong>, o —\nsi tu objetivo es un agente de IA en lugar de una puerta de enlace programable — <strong><a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/add-whatsapp-channel-openclaw/\">el canal nativo de WhatsApp de OpenClaw</a></strong>, que maneja la sesión por ti.</li>\n</ul>\n<p>Evolution brilla para <strong>automatización saliente a números que controlas</strong> (notificaciones, alertas, flujos). Como asistente de IA bidireccional en una cuenta personal de LID, trata la entrega como un esfuerzo mejor.</p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"resumen-de-solución-de-problemas\">Resumen de solución de problemas<a href=\"https://wec.wiline.com/docs/es/tutorials/whatsapp-ai-assistant-evolution-api/#resumen-de-soluci%C3%B3n-de-problemas\" class=\"hash-link\" aria-label=\"Enlace directo al Resumen de solución de problemas\" title=\"Enlace directo al Resumen de solución de problemas\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\"><strong><code>acceso denegado al pull</code> en <code>atendai/evolution-api</code></strong> → imagen movida a <code>evoapicloud/evolution-api</code>.</li>\n<li class=\"\"><strong>Instancia atascada en <code>conectando</code>, bucle de reinicio</strong> → versión de Baileys obsoleta; fija <code>CONFIG_SESSION_PHONE_VERSION</code>.</li>\n<li class=\"\"><strong>El puente se responde a sí mismo para siempre</strong> → falta el guardia de bucle <code>fromMe</code>.</li>\n<li class=\"\"><strong>Inundación de mensajes de grupo / tiempos de espera de envío</strong> → filtra <code>@g.us</code>; responde solo a DMs.</li>\n<li class=\"\"><strong><code>ENVIAR -&gt; 400 exists:false ...@lid</code></strong> → resuelve el LID al JID de teléfono a través de <code>findContacts</code>.</li>\n<li class=\"\"><strong><code>**asteriscos**</code> en respuestas</strong> → traduce Markdown al formato de WhatsApp.</li>\n<li class=\"\"><strong>\"Esperando este mensaje\"</strong> → pared de cifrado de Baileys/LID; un nuevo re-vinculo es una ventana temporal; usa un número no LID / API Cloud para producción.</li>\n</ul>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"desmantelamiento\">Desmantelamiento<a href=\"https://wec.wiline.com/docs/es/tutorials/whatsapp-ai-assistant-evolution-api/#desmantelamiento\" class=\"hash-link\" aria-label=\"Enlace directo al Desmantelamiento\" title=\"Enlace directo al Desmantelamiento\" translate=\"no\">​</a></h2>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token builtin class-name\">cd</span><span class=\"token plain\"> ~/evolution-api </span><span class=\"token operator\" style=\"color:#393A34\">&amp;&amp;</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose down          </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># agrega -v para también borrar la DB/sesión</span><br></div></code></pre></div></div>\n<p>Desvincula el dispositivo en <strong>WhatsApp → Dispositivos vinculados</strong> si has terminado.</p>\n<div class=\"skillComplete\"><span class=\"skillComplete__medal\" aria-hidden=\"true\">🎯</span><div class=\"skillComplete__text\"><div class=\"skillComplete__title\">Finished this tutorial?</div><div class=\"skillComplete__sub\">Mark it complete to earn <strong>Self-host a WhatsApp AI bridge</strong> on your skill path.</div></div><button type=\"button\" class=\"skillComplete__btn\">Mark as complete ✓</button></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"qué-sigue\">¿Qué sigue?<a href=\"https://wec.wiline.com/docs/es/tutorials/whatsapp-ai-assistant-evolution-api/#qu%C3%A9-sigue\" class=\"hash-link\" aria-label=\"Enlace directo al ¿Qué sigue?\" title=\"Enlace directo al ¿Qué sigue?\" translate=\"no\">​</a></h2>\n<p>Has construido el patrón de puente reutilizable — webhook → resolver → modelo → respuesta. Apúntalo a un cerebro diferente, a un canal diferente, o agrega herramientas. Si deseas un agente de IA en WhatsApp sin las advertencias de Baileys, el <strong><a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/add-whatsapp-channel-openclaw/\">canal nativo de WhatsApp de OpenClaw</a></strong> es el camino gestionado; para respuestas fundamentadas en documentos, la <strong><a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/\">serie de evals y observabilidad</a></strong> construye el servicio RAG en el que se conecta este tutorial.</p>",
            "url": "https://wec.wiline.com/docs/es/tutorials/whatsapp-ai-assistant-evolution-api/",
            "title": "Crea un asistente de IA para WhatsApp desde cero con Evolution y la API de WEC",
            "summary": "Autoalberga una puerta de enlace programable de WhatsApp (API de Evolution) y escribe el puente de ~50 líneas que lo convierte en un asistente de IA: webhook entrante, LLM saliente, respuesta de vuelta. Cada detalle es real: la imagen que movió a los editores, el bucle de la versión de Baileys, el guardia de bucle, el spam en grupos, la dirección LID de WhatsApp y la pared de entrega de la que nadie te advierte.",
            "date_modified": "2026-07-21T00:00:00.000Z",
            "author": {
                "name": "Rafael Fernandes",
                "url": "https://www.linkedin.com/in/rafaelmacariofernandes/"
            },
            "tags": [
                "ai",
                "self-hosting",
                "whatsapp",
                "evolution-api",
                "docker",
                "inference"
            ]
        },
        {
            "id": "https://wec.wiline.com/docs/es/tutorials/add-whatsapp-channel-openclaw/",
            "content_html": "<div class=\"tutorialHero\"><img class=\"tutorialHero__brand\" src=\"https://wec.wiline.com/docs/es/assets/images/openclaw-wordmark-003352a1a7f02afc3bd877ae6f2dc175.png\" alt=\"OpenClaw\"><span class=\"tutorialHero__plus\">+</span><img class=\"tutorialHero__brand\" src=\"https://wec.wiline.com/docs/es/assets/images/whatsapp_logo-cc34546fcff90d36128c7230c48c496f.png\" alt=\"WhatsApp\"></div>\n<div class=\"skillTracker\" tabindex=\"0\" aria-label=\"Skill path: 0 of 6 skills earned\"><div class=\"skillTracker__ring\" style=\"--skillTracker-pct:0%\"><span class=\"skillTracker__ringLabel\">0<!-- -->/<!-- -->6</span></div><div class=\"skillTracker__panel\"><div class=\"skillTracker__header\"><span class=\"skillTracker__title\">🎯 Skill path</span><span class=\"skillTracker__count\">0<!-- -->/<!-- -->6<!-- --> earned</span></div><div class=\"skillTracker__series\">Self-hosting OpenClaw</div><ul class=\"skillTracker__steps\"><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/\"><span class=\"skillTracker__dot\" data-state=\"locked\">1</span><span class=\"skillTracker__skill\" data-state=\"locked\">Deploy your own AI assistant</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/\"><span class=\"skillTracker__dot\" data-state=\"locked\">2</span><span class=\"skillTracker__skill\" data-state=\"locked\">Real HTTPS + auth</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/add-telegram-channel-openclaw/\"><span class=\"skillTracker__dot\" data-state=\"locked\">3</span><span class=\"skillTracker__skill\" data-state=\"locked\">Chat from Telegram</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/\"><span class=\"skillTracker__dot\" data-state=\"locked\">4</span><span class=\"skillTracker__skill\" data-state=\"locked\">Private mesh access</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/run-openclaw-on-wec-models/\"><span class=\"skillTracker__dot\" data-state=\"locked\">5</span><span class=\"skillTracker__skill\" data-state=\"locked\">Run it on WEC models</span></a></li><li><span class=\"skillTracker__step\"><span class=\"skillTracker__dot\" data-state=\"current\">🏆</span><span class=\"skillTracker__skill\" data-state=\"current\">Chat from WhatsApp</span></span></li></ul><div class=\"skillTracker__footer\">Finish this tutorial, then mark it complete below to earn the skill.</div></div></div>\n<p>Telegram (<a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/add-telegram-channel-openclaw/\">Parte 3</a>) le dio a tu agente un <strong>bot</strong>.\nWhatsApp le da una <strong>línea telefónica</strong> — la aplicación que ~3 mil millones de personas ya usan, accesible sin fricción. Una advertencia que vale la pena entender desde el principio: WhatsApp no tiene cuenta de bot, así que OpenClaw se vincula a un número real como un <strong>dispositivo compañero</strong> (como WhatsApp Web) y el agente actúa <em>como esa cuenta</em>. Cada comando y error a continuación proviene de una ejecución real.</p>\n<!-- -->\n<div class=\"theme-admonition theme-admonition-info admonition_xJq3 alert alert--info\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z\"></path></svg></span>Reproducibilidad</div><div class=\"admonitionContent_BuS1\"><p>Continúa desde las Partes 1–4 —\n<a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/\">desplegar</a>,\n<a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/\">HTTPS</a>,\n<a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/add-telegram-channel-openclaw/\">Telegram</a>,\n<a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/\">NetBird</a> — en OpenClaw <strong>2026.7.1</strong>. WhatsApp se vincula\npor <strong>QR</strong>, sin necesidad de webhook o dominio público.</p></div></div>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"lo-que-construirás\">Lo que construirás<a href=\"https://wec.wiline.com/docs/es/tutorials/add-whatsapp-channel-openclaw/#lo-que-construir%C3%A1s\" class=\"hash-link\" aria-label=\"Enlace directo al Lo que construirás\" title=\"Enlace directo al Lo que construirás\" translate=\"no\">​</a></h2>\n<p>Cualquiera envía un mensaje al número; la puerta de enlace — vinculada como un dispositivo compañero — lo procesa a través de tu modelo y responde <strong>desde el número mismo</strong>. Sin puertos de entrada, sin cuenta de bot.</p>\n<!-- -->\n<p><strong>Requisitos previos:</strong> Partes 1–4 completadas (OpenClaw en <code>~/openclaw</code>), una cuenta de WhatsApp en tu\nteléfono, ~1 GB de espacio libre en disco. Todos los comandos se ejecutan en <code>~/openclaw</code>.</p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-1--agregar-el-canal-de-whatsapp\">Paso 1 — Agregar el canal de WhatsApp<a href=\"https://wec.wiline.com/docs/es/tutorials/add-whatsapp-channel-openclaw/#paso-1--agregar-el-canal-de-whatsapp\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 1 — Agregar el canal de WhatsApp\" title=\"Enlace directo al Paso 1 — Agregar el canal de WhatsApp\" translate=\"no\">​</a></h2>\n<p>OpenClaw trata las aplicaciones de mensajería como <em>canales</em>; WhatsApp es uno de primera clase:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose run </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rm</span><span class=\"token plain\"> openclaw-cli channels </span><span class=\"token function\" style=\"color:#d73a49\">add</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--channel</span><span class=\"token plain\"> whatsapp</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Plugin de WhatsApp instalado</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Cuenta de WhatsApp \"default\" añadida.</span><br></div></code></pre></div></div>\n<details class=\"details_lb9f alert alert--info details_b_Ee\" data-collapsed=\"true\"><summary><b>¿Recibiste <code>requires plugin API &gt;=2026.7.1</code>?</b> Estás en una versión anterior de OpenClaw — actualiza primero.</summary><div><div class=\"collapsibleContent_i85q\"><p>El canal de WhatsApp es un plugin restringido a versiones más nuevas:</p><p><span class=\"zoomImage__wrap\"><img alt=\"El error de versión en OpenClaw 2026.6.8\" src=\"https://wec.wiline.com/docs/es/assets/images/wa-version-wall-db236509ca6e250b19147327473e0932.png\" width=\"861\" height=\"592\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 1.</strong> La pared de versión — el plugin de WhatsApp necesita OpenClaw ≥2026.7.1.</p><p>Haz una copia de seguridad, descarga, recrea y confirma que tus canales existentes sobrevivieron (<code>:latest</code> no\n<strong>se actualiza</strong> automáticamente — debes descargar deliberadamente):</p><div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">cp</span><span class=\"token plain\"> ~/.openclaw/openclaw.json ~/.openclaw/openclaw.json.bak-</span><span class=\"token variable\" style=\"color:#36acaa\">$(</span><span class=\"token variable function\" style=\"color:#d73a49\">date</span><span class=\"token variable\" style=\"color:#36acaa\"> +%Y%m%d</span><span class=\"token variable\" style=\"color:#36acaa\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose pull openclaw-gateway</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose run </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rm</span><span class=\"token plain\"> openclaw-cli </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--version</span><span class=\"token plain\">          </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># espera 2026.7.1+</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose up </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> --force-recreate openclaw-gateway</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose run </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rm</span><span class=\"token plain\"> openclaw-cli channels status    </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># Telegram debería seguir conectado</span><br></div></code></pre></div></div><p><span class=\"zoomImage__wrap\"><img alt=\"La imagen descargada informa 2026.7.1\" src=\"https://wec.wiline.com/docs/es/assets/images/wa-version-check-b1b98be0f2990d46e5686130b2722d3b.png\" width=\"695\" height=\"156\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 2.</strong> Después de descargar, la imagen informa 2026.7.1 — se cumple el requisito.</p><p>Luego vuelve a ejecutar el <code>channels add</code> anterior.</p></div></div></details>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-2--confiar-en-el-plugin\">Paso 2 — Confiar en el plugin<a href=\"https://wec.wiline.com/docs/es/tutorials/add-whatsapp-channel-openclaw/#paso-2--confiar-en-el-plugin\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 2 — Confiar en el plugin\" title=\"Enlace directo al Paso 2 — Confiar en el plugin\" translate=\"no\">​</a></h2>\n<p>Verifica el estado y el canal está <strong>detenido</strong> con un error críptico:</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"WhatsApp detenido con el error de confianza openKeyedStore\" src=\"https://wec.wiline.com/docs/es/assets/images/wa-trust-error-7a8033b008450d82190f783b79998f71.png\" width=\"861\" height=\"293\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 3.</strong> <code>detenido</code> — un plugin no confiable no puede usar <code>openKeyedStore</code>, por lo que el canal no se iniciará.</p>\n<p>Esa es una <strong>característica de seguridad</strong>: OpenClaw aísla los plugins de terceros, y uno no confiable no puede usar <code>openKeyedStore</code> (el almacenamiento que WhatsApp necesita para su sesión). Confía en él —\nincluye cualquier otro plugin no empaquetado que ya esté cargando (aquí <code>codex</code>) para que no lo bloquees\n— y recrea:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose run </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rm</span><span class=\"token plain\"> openclaw-cli config </span><span class=\"token builtin class-name\">set</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  --batch-json </span><span class=\"token string\" style=\"color:#e3116c\">'[{\"path\":\"plugins.allow\",\"value\":[\"codex\",\"whatsapp\"]}]'</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose up </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> --force-recreate openclaw-gateway</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose run </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rm</span><span class=\"token plain\"> openclaw-cli channels status</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"plugins.allow ahora lista codex y whatsapp\" src=\"https://wec.wiline.com/docs/es/assets/images/wa-trust-fix-503842c5815cf95dc4bde07dc3b4a6ee.png\" width=\"673\" height=\"105\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 4.</strong> Confiado — <code>plugins.allow</code> ahora lista <code>whatsapp</code> (y <code>codex</code>).</p>\n<p>El reinicio es importante — la puerta de enlace lee <code>plugins.allow</code> <em>antes</em> de inicializar el\nplugin. Ahora está en funcionamiento:</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"WhatsApp funcionando y conectado, Telegram sigue activo\" src=\"https://wec.wiline.com/docs/es/assets/images/wa-connected-d4ec6a75486c355d24570487f62305c8.png\" width=\"860\" height=\"294\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 5.</strong> En vivo — WhatsApp <code>funcionando, conectado, saludable</code>, y Telegram sigue activo.</p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-3--vincula-tu-teléfono-por-qr\">Paso 3 — Vincula tu teléfono por QR<a href=\"https://wec.wiline.com/docs/es/tutorials/add-whatsapp-channel-openclaw/#paso-3--vincula-tu-tel%C3%A9fono-por-qr\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 3 — Vincula tu teléfono por QR\" title=\"Enlace directo al Paso 3 — Vincula tu teléfono por QR\" translate=\"no\">​</a></h2>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose run </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rm</span><span class=\"token plain\"> openclaw-cli channels login</span><br></div></code></pre></div></div>\n<p>Escanea el QR del terminal desde <strong>WhatsApp → Configuración → Dispositivos Vinculados → Vincular un Dispositivo</strong>.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El QR de emparejamiento, vinculado después del reinicio del código 515\" src=\"https://wec.wiline.com/docs/es/assets/images/wa-qr-544d7332e052ce0469c77d7e60fec42a.png\" width=\"786\" height=\"834\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 6.</strong> Escanea para vincular; <code>✅ Vinculado después del reinicio</code> maneja la reconexión del <code>código 515</code>.</p>\n<div class=\"theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z\"></path></svg></span>El reinicio del <code>código 515</code> es normal</div><div class=\"admonitionContent_BuS1\"><p>WhatsApp obliga a una reconexión justo después del emparejamiento; OpenClaw lo maneja automáticamente. Este\nes exactamente el lugar donde las configuraciones manuales se quedan atrapadas en un bucle — aquí simplemente funciona.</p></div></div>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-4--pruébalo\">Paso 4 — Pruébalo<a href=\"https://wec.wiline.com/docs/es/tutorials/add-whatsapp-channel-openclaw/#paso-4--pru%C3%A9balo\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 4 — Pruébalo\" title=\"Enlace directo al Paso 4 — Pruébalo\" translate=\"no\">​</a></h2>\n<p>Desde <strong>otro teléfono</strong>, envía un mensaje al número:</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El agente de OpenClaw respondiendo en WhatsApp\" src=\"https://wec.wiline.com/docs/es/assets/images/wa-reply-08f4125cb353af3cff9a2418928be7ec.png\" width=\"1320\" height=\"2868\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 7.</strong> Una pregunta enviada, la respuesta del agente — enviada desde el número, como la cuenta.</p>\n<p>Responde — pero nota que responde <strong>desde tu número, como tú.</strong> Eso es el modo compañero:\nel agente <em>es</em> la cuenta. Genial para un asistente personal, y la razón de la advertencia\na continuación.</p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Habilidad desbloqueada 🏅</div><div class=\"admonitionContent_BuS1\"><p>Tu agente de OpenClaw responde en WhatsApp — de forma nativa, sin puente, sin cuenta de bot.</p></div></div>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"antes-de-un-número-real\">Antes de un número real<a href=\"https://wec.wiline.com/docs/es/tutorials/add-whatsapp-channel-openclaw/#antes-de-un-n%C3%BAmero-real\" class=\"hash-link\" aria-label=\"Enlace directo al Antes de un número real\" title=\"Enlace directo al Antes de un número real\" translate=\"no\">​</a></h2>\n<div class=\"theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z\"></path></svg></span>Con un número personal, el agente responde a todos, como tú</div><div class=\"admonitionContent_BuS1\"><p>Procesa <strong>cada DM y grupo</strong> que recibe la cuenta (<code>Escuchando ... DM + todos los grupos</code>). Para cualquier cosa real: vincula un <strong>número dedicado</strong> (una SIM prepago barata — los números virtuales son a menudo rechazados), y establece una <strong>lista de permitidos</strong> antes de exponerlo.</p></div></div>\n<p>Estaciona el canal cuando termines de probar (Telegram sigue funcionando; tu enlace permanece guardado):</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose run </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rm</span><span class=\"token plain\"> openclaw-cli config </span><span class=\"token builtin class-name\">set</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  --batch-json </span><span class=\"token string\" style=\"color:#e3116c\">'[{\"path\":\"channels.whatsapp.accounts.default.enabled\",\"value\":false}]'</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose up </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> --force-recreate openclaw-gateway</span><br></div></code></pre></div></div>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"solución-de-problemas\">Solución de problemas<a href=\"https://wec.wiline.com/docs/es/tutorials/add-whatsapp-channel-openclaw/#soluci%C3%B3n-de-problemas\" class=\"hash-link\" aria-label=\"Enlace directo al Solución de problemas\" title=\"Enlace directo al Solución de problemas\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\"><strong><code>openKeyedStore solo está disponible para plugins confiables</code></strong> — el canal se instala pero permanece\n<code>detenido</code>. Agrega el id a <code>plugins.allow</code> y <strong>recrea</strong> la puerta de enlace (Paso 2). Un cambio de configuración en vivo no es suficiente una vez que ya ha fallado al iniciar.</li>\n<li class=\"\"><strong>Bucle de QR / <code>código 515</code></strong> — reinicio normal después del emparejamiento. Si nunca se estabiliza, elimina\nentradas obsoletas en WhatsApp → Dispositivos Vinculados y escanea un QR nuevo.</li>\n<li class=\"\"><strong>Respondió a un contacto real</strong> — el modo compañero responde a todo; consulta la advertencia\nanterior y usa un número dedicado.</li>\n</ul>\n<hr>\n<div class=\"skillComplete\"><span class=\"skillComplete__medal\" aria-hidden=\"true\">🎯</span><div class=\"skillComplete__text\"><div class=\"skillComplete__title\">Finished this tutorial?</div><div class=\"skillComplete__sub\">Mark it complete to earn <strong>Chat from WhatsApp</strong> on your skill path.</div></div><button type=\"button\" class=\"skillComplete__btn\">Mark as complete ✓</button></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"qué-sigue\">¿Qué sigue?<a href=\"https://wec.wiline.com/docs/es/tutorials/add-whatsapp-channel-openclaw/#qu%C3%A9-sigue\" class=\"hash-link\" aria-label=\"Enlace directo al ¿Qué sigue?\" title=\"Enlace directo al ¿Qué sigue?\" translate=\"no\">​</a></h2>\n<p>Parte 5 completada — tu agente llega a Telegram <em>y</em> WhatsApp desde una caja privada y reforzada.</p>\n<ul>\n<li class=\"\">Dale <strong>memoria que sobreviva a los reinicios</strong> — la serie del <strong><a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/self-host-hermes-agent/\">Agente Hermes</a></strong>.</li>\n<li class=\"\">Fundamenta en <em>tus</em> documentos — la serie de <strong><a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/\">evaluaciones de IA y\nobservabilidad</a></strong> construye un servicio RAG al que puedes apuntar un canal.</li>\n</ul>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"desmantelamiento\">Desmantelamiento<a href=\"https://wec.wiline.com/docs/es/tutorials/add-whatsapp-channel-openclaw/#desmantelamiento\" class=\"hash-link\" aria-label=\"Enlace directo al Desmantelamiento\" title=\"Enlace directo al Desmantelamiento\" translate=\"no\">​</a></h2>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose run </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rm</span><span class=\"token plain\"> openclaw-cli channels remove </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--channel</span><span class=\"token plain\"> whatsapp</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose restart openclaw-gateway</span><br></div></code></pre></div></div>\n<p>Y desvincula el dispositivo en <strong>WhatsApp → Dispositivos Vinculados</strong> si has terminado.</p>",
            "url": "https://wec.wiline.com/docs/es/tutorials/add-whatsapp-channel-openclaw/",
            "title": "Agregar un canal de WhatsApp a OpenClaw",
            "summary": "Coloca tu agente autoalojado en WhatsApp. Agrega el canal, confía en el plugin, escanea un QR — y entiende por qué un enlace de compañero hace que el agente actúe como tu cuenta. Cada comando y error de una ejecución real.",
            "date_modified": "2026-07-20T00:00:00.000Z",
            "author": {
                "name": "Rafael Fernandes",
                "url": "https://www.linkedin.com/in/rafaelmacariofernandes/"
            },
            "tags": [
                "ai",
                "self-hosting",
                "openclaw",
                "whatsapp",
                "chatbot"
            ]
        },
        {
            "id": "https://wec.wiline.com/docs/es/tutorials/deepeval-regression-testing-wiline-inference/",
            "content_html": "<div class=\"tutorialHero\"><img class=\"tutorialHero__deepeval\" src=\"data:image/png;base64,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\" alt=\"DeepEval\"><span class=\"tutorialHero__plus\">+</span><svg viewBox=\"0 0 24 24\" fill=\"#2496ED\" aria-label=\"Docker\" class=\"tutorialHero__docker\"><path d=\"M13.983 11.078h2.119a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.119a.185.185 0 00-.185.185v1.888c0 .102.083.185.185.185m-2.954-5.43h2.118a.186.186 0 00.186-.186V3.574a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m0 2.716h2.118a.187.187 0 00.186-.186V6.29a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.887c0 .102.082.185.185.186m-2.93 0h2.12a.186.186 0 00.184-.186V6.29a.185.185 0 00-.185-.185H8.1a.185.185 0 00-.185.185v1.887c0 .102.083.185.185.186m-2.964 0h2.119a.186.186 0 00.185-.186V6.29a.185.185 0 00-.185-.185H5.136a.186.186 0 00-.186.185v1.887c0 .102.084.185.186.186m5.893 2.715h2.118a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m-2.93 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.083.185.185.185m-2.964 0h2.119a.185.185 0 00.185-.185V9.006a.185.185 0 00-.184-.186h-2.12a.186.186 0 00-.186.186v1.887c0 .102.084.185.186.185m-2.92 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.082.185.185.185M23.763 9.89c-.065-.051-.672-.51-1.954-.51-.338.001-.676.03-1.01.087-.248-1.7-1.653-2.53-1.716-2.566l-.344-.199-.226.327c-.284.438-.49.922-.612 1.43-.23.97-.09 1.882.403 2.661-.595.332-1.55.413-1.744.42H.751a.751.751 0 00-.75.748 11.376 11.376 0 00.692 4.062c.545 1.428 1.355 2.48 2.41 3.124 1.18.723 3.1 1.137 5.275 1.137.983.003 1.963-.086 2.93-.266a12.248 12.248 0 003.823-1.389c.98-.567 1.86-1.288 2.61-2.136 1.252-1.418 1.998-2.997 2.553-4.4h.221c1.372 0 2.215-.549 2.68-1.009.309-.293.55-.65.707-1.046l.098-.288Z\"></path></svg><span class=\"tutorialHero__plus\">+</span><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"242\" height=\"255\" fill=\"none\" viewBox=\"0 0 242 255\" class=\"tutorialHero__wiline\"><path fill=\"#4E8EF7\" d=\"M56.711 11.293A195 195 0 0 1 41.899 4.79c4.788 1.85 9.71 4.028 14.812 6.504M73.807 29.35c-1.302-1.446-2.666-3.233-4.02-5.38 1.326 1.44 2.642 3.24 4.02 5.38M69.761 23.354c-.832-.585-1.752-1.472-2.558-2.714.9.567 1.685 1.49 2.558 2.714M62.97 15.774a10.7 10.7 0 0 1-3.158-1.841c.993.299 1.986.956 3.159 1.841M59.715 13.346c-.818-.194-1.782-.674-2.829-1.494.84.175 1.761.691 2.83 1.494M39.317 3.782c-.524.192-1.413.302-2.455.14.594-.255 1.342-.238 2.455-.14M74.906 31.339c-.334-.153-.73-.61-1.117-1.398.358.143.706.619 1.117 1.398M66.024 18.878c-.239.1-.557.05-.752-.276.35-.201.532-.069.752.276M71.773 150.625c3.363 7.291 7.185 14.411 9.983 21.914 4.474 11.997 11.167 20.832 24.296 24.757 10.62 3.175 20.369 1.795 30.093-1.816 9.937-3.689 15.165-11.978 18.501-21.541 4.349-12.466 9.385-24.443 19.668-33.446 11.32-9.909 24.639-14.788 39.429-15.63 8.3-.473 16.649-.081 25.444.444.472 15.673.385 30.825.55 45.974.038 3.499-1.049 4.719-4.557 4.543-4.982-.25-9.987-.079-14.98.009-.209.004-.4.991-.6 1.521-23.106 1.695-37.691 15.22-47.961 34.651-3.387 6.408-5.913 13.38-10.013 19.273-4.368 6.277-10.134 11.582-15.853 17.196-6.439 1.421-12.244 3.342-18.202 4.416-12.627 2.274-24.661.457-35.764-6.356-1.028-.631-2.395-.709-3.604-1.043 0 0-.117-.11-.047-.495-.818-.973-1.706-1.562-2.593-2.15 0 0 .09-.044.05-.335-.513-.545-.988-.798-1.463-1.052 0 0-.07-.123.012-.492-.561-.894-1.205-1.421-1.848-1.947 0 0 .134-.068.102-.28-.321-.442-.61-.671-.898-.901 0 0 .086-.007.074-.336-2.05-3.429-4.089-6.529-6.127-9.629 0 0 .094-.017.123-.365-3.013-6.117-6.056-11.886-9.1-17.655 0 0 .096-.01.088-.342-1.978-4.073-3.89-7.848-5.997-11.51-.246-.428-1.572-.235-2.398-.329 0 0-.226-.202-.114-.613-.213-.499-.537-.587-.861-.674 0 0-.032-.101.02-.455-.62-.846-1.291-1.339-1.964-1.832 0 0 .133-.083.119-.372-2.476-4.624-5.645-8.048-10.914-8.777 0 0 .108-.031.01-.326-1.673-1.444-3.105-2.918-4.85-3.688-9.751-4.308-20.013-5.643-30.63-5.23-7.305.285-7.316.051-7.317-7.446q-.002-18.496-.002-36.992v-3.57h36.106s-.08.019.133.283c1.43.415 2.646.567 3.863.719 0 0-.077.022.113.296 1.804.591 3.417.908 5.03 1.225 0 0 .139-.037.152.369 1.592.962 3.17 1.517 4.748 2.073 0 0-.091.027.052.267.735.415 1.328.59 1.92.765 0 0-.076.033.053.246.477.325.825.438 1.173.551 0 0-.149.018-.07.246.418.239.757.251 1.096.263 0 0 .23.058.189.465 1.522 1.319 3.084 2.231 4.647 3.143 0 0-.105.033-.015.316 2.723 2.764 5.356 5.244 7.989 7.724 0 0-.104.062-.094.352 1.01 1.401 2.01 2.512 3.01 3.624\"></path><path fill=\"#4E8EF7\" d=\"M214.77 52.819c-3.343.846-6.723 1.574-10.024 2.562-11.388 3.408-20.567 10.142-27.166 19.865-5.097 7.511-9.621 15.483-13.685 23.608-6.809 13.613-15.725 24.809-31.343 28.198-24.707 5.362-45.22-.126-57.852-24.564C69.466 92.363 63.8 82.375 57.349 73c-3.369-4.896-8.866-8.326-13.679-13.041-1.11-1.409-1.81-2.472-2.794-2.937-10.697-5.056-21.87-7.3-33.686-5.344-3.877.641-5.72-.297-5.615-4.86.294-12.816.26-25.646.019-38.464C1.509 3.874 3.15 2.5 7.429 2.716c9.774.492 19.564.678 29.347.989 0 0-.076.026.082.257 1.046.122 1.935.012 2.825-.098.575.043 1.15.086 2.038.692 5.265 2.72 10.218 4.877 15.17 7.034 0 0-.108.03-.016.317 1.058.765 2.022 1.245 2.987 1.724 0 0-.113.048-.082.354 1.144.876 2.258 1.446 3.372 2.017 0 0 .18.145.097.506.223.404.53.447.837.49 0 0 .214.13.144.504.244.426.558.477.873.528 0 0 .171.124.108.485.256.412.574.464.893.515 0 0 .167.127.105.493.265.437.592.509.919.58 0 0 .078.108.019.5.861 1.28 1.782 2.166 2.702 3.053 0 0-.101.061-.087.368 1.379 2.093 2.743 3.88 4.107 5.666 0 0-.094.011-.092.297.4.742.795 1.2 1.19 1.656 0 0-.121.02-.165.337.685 1.432 1.413 2.549 2.14 3.665 0 0-.11.024-.129.299.326.776.67 1.277 1.014 1.78 0 0-.084-.005-.1.367.933 4.079.311 8.497 4.648 10.67 4.69 12.164 12.107 21.794 25.852 23.497 19.618 2.431 35.321.062 45.299-24.554 3.398-8.383 7.099-16.902 12.232-24.264 8.353-11.979 21.046-17.795 35.832-20.32 1.666.417 2.649.651 3.624.622 9.93-.298 19.865-.513 29.785-1.032 3.818-.2 4.825 1.198 4.746 4.853-.218 9.967.004 19.944-.109 29.915-.052 4.583 1.149 10.606-1.239 13.302-2.044 2.308-8.394.86-12.842.967-3.82.092-7.644.036-11.467.045.065.31.097.633.213.922.034.084.306.072.469.105M37.585 127.354c-11.585.344-23.368.344-35.907.344v3.57q0 18.496.002 36.992c0 7.497.012 7.731 7.318 7.446 10.616-.413 20.878.922 30.63 5.23 1.743.77 3.176 2.244 4.84 3.744-13.655-7.873-28.93-7.767-44.468-7.398v-52.503c12.516.308 25.063-1.222 37.585 2.575\"></path><path fill=\"#4E8EF7\" d=\"M36.619 3.401c-9.626-.007-19.415-.193-29.19-.685C3.151 2.5 1.51 3.874 1.594 8.354c.241 12.818.275 25.648-.018 38.464-.105 4.563 1.737 5.501 5.614 4.86 11.816-1.956 22.99.288 33.686 5.344.984.465 1.685 1.529 2.616 2.66-13.39-7.538-28.284-7.343-43.479-6.78V0C12.314 1.045 24.388 2.071 36.62 3.401M215.232 52.852c-.625-.066-.897-.054-.931-.138-.116-.29-.148-.612-.212-.922 3.822-.01 7.646.047 11.467-.045 4.448-.108 10.797 1.341 12.842-.967 2.387-2.696 1.186-8.719 1.238-13.302.114-9.971-.109-19.948.109-29.915.08-3.655-.928-5.052-4.746-4.853-9.919.519-19.855.734-29.785 1.032-.975.03-1.958-.205-3.228-.58C214.559 1.945 227.423.991 240.73.003v52.882c-8.276 0-16.656 0-25.498-.033M220.071 177.398c-.271-.573-.08-1.561.129-1.565 4.993-.088 9.998-.259 14.98-.009 3.508.176 4.595-1.044 4.557-4.543-.165-15.149-.078-30.301-.088-45.911.458.372 1.32 1.202 1.324 2.036.079 16.424.059 32.848.059 50.036-6.984 0-13.737 0-20.961-.044M88.39 245.741c1.022.084 2.39.162 3.417.793 11.102 6.813 23.137 8.63 35.763 6.356 5.959-1.073 11.764-2.995 17.871-4.34-5.612 2.132-11.343 5.166-17.325 5.79-13.898 1.451-27.803 1.213-39.725-8.599M66.556 210.212c2.975 5.411 6.018 11.18 9.02 17.344-3.011-5.4-5.982-11.193-9.02-17.344M58.341 197.934c.666-.167 1.992-.36 2.238.068 2.107 3.662 4.018 7.437 5.985 11.557-2.69-3.535-5.376-7.449-8.223-11.625M44.637 185.241c5.099.438 8.268 3.862 10.737 8.564-3.518-2.513-7.043-5.393-10.737-8.564M82.29 48.422c-4.252-1.834-3.63-6.252-4.548-10.37 1.508 3.069 2.986 6.55 4.548 10.37M68.757 146.326c-2.533-2.157-5.166-4.637-7.882-7.455 2.539 2.151 5.16 4.641 7.882 7.455M75.532 228.226a122 122 0 0 1 6.047 9.319c-1.993-2.742-3.986-5.854-6.047-9.319M71.695 150.312c-.922-.799-1.921-1.91-2.906-3.366.952.788 1.89 1.921 2.906 3.366M46.663 130.007c-1.353-.103-2.966-.42-4.767-1.051 1.377.07 2.941.454 4.767 1.051M51.647 132.369a13.1 13.1 0 0 1-4.501-1.77c1.48.227 2.902.852 4.5 1.77M60.724 138.321a17.7 17.7 0 0 1-4.395-2.781c1.501.609 2.869 1.551 4.395 2.781M76.889 35.31c-.674-.781-1.402-1.898-2.068-3.39.711.768 1.362 1.912 2.068 3.39M85.668 243.124c.782.31 1.67.899 2.423 1.825-.862-.29-1.59-.919-2.423-1.825M41.596 128.372c-1.033.176-2.25.024-3.68-.432 1.023-.169 2.26-.032 3.68.432M55.367 194.37c.568.222 1.24.715 1.808 1.548-.637-.199-1.17-.738-1.808-1.548M82.397 239.295c.56.251 1.204.778 1.71 1.628-.633-.235-1.13-.794-1.71-1.628M84.291 241.679c.334.034.809.287 1.298.875-.376.004-.766-.326-1.298-.875M53.64 133.426c-.438.094-1.03-.081-1.764-.543.441-.101 1.026.086 1.764.543M77.805 37.409c-.322-.188-.666-.69-.978-1.52.34.182.648.693.978 1.52M81.588 238.108c.218-.039.507.19.823.707-.233.045-.493-.196-.823-.707M55.906 134.832c-.198.157-.537.145-.92-.161.262-.29.537-.195.92.161M57.276 196.552c.254-.079.578.009.756.361-.377.171-.557.018-.756-.361M54.888 134.25c-.215.129-.563.017-1.038-.351.217-.133.56-.012 1.038.351M64.014 16.858a.545.545 0 0 1-.71-.247c.344-.19.51-.067.71.247M65.027 17.882a.574.574 0 0 1-.734-.272c.35-.193.525-.063.734.272M67.045 19.939c-.244.093-.57.021-.777-.329.358-.192.55-.044.777.329\"></path></svg></div>\n<div class=\"skillTracker\" tabindex=\"0\" aria-label=\"Skill path: 0 of 8 skills earned\"><div class=\"skillTracker__ring\" style=\"--skillTracker-pct:0%\"><span class=\"skillTracker__ringLabel\">0<!-- -->/<!-- -->8</span></div><div class=\"skillTracker__panel\"><div class=\"skillTracker__header\"><span class=\"skillTracker__title\">🎯 Skill path</span><span class=\"skillTracker__count\">0<!-- -->/<!-- -->8<!-- --> earned</span></div><div class=\"skillTracker__series\">AI evals &amp; observability</div><ul class=\"skillTracker__steps\"><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">1</span><span class=\"skillTracker__skill\" data-state=\"locked\">Prove a model works</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">2</span><span class=\"skillTracker__skill\" data-state=\"locked\">Trustworthy JSON</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/synthetic-eval-datasets-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">3</span><span class=\"skillTracker__skill\" data-state=\"locked\">Real test data at scale</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/\"><span class=\"skillTracker__dot\" data-state=\"locked\">4</span><span class=\"skillTracker__skill\" data-state=\"locked\">Observe &amp; score production</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">5</span><span class=\"skillTracker__skill\" data-state=\"locked\">RAG, end to end</span></a></li><li><span class=\"skillTracker__step\"><span class=\"skillTracker__dot\" data-state=\"current\">6</span><span class=\"skillTracker__skill\" data-state=\"current\">Catch regressions in CI</span></span></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/component-tracing-agent-tool-calls-wiline/\"><span class=\"skillTracker__dot\" data-state=\"locked\">7</span><span class=\"skillTracker__skill\" data-state=\"locked\">Trace &amp; debug agent tool calls</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/web-search-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">🏆</span><span class=\"skillTracker__skill\" data-state=\"locked\">Add live web search</span></a></li></ul><div class=\"skillTracker__footer\">Finish this tutorial, then mark it complete below to earn the skill.</div></div></div>\n<p>Nuestro <a class=\"\" href=\"https://wec.wiline.com/docs/es/news/gpt-5-6-token-economics/\">análisis de economía de tokens de GPT-5.6</a> terminó con un desafío:\nlas promesas de referencia son una hipótesis, no una razón — ejecuta la evaluación en <em>tu</em> carga de trabajo antes de que\ncreas en un reclamo de \"menos tokens por tarea\". Este tutorial es nosotros siguiendo nuestro propio consejo.</p>\n<p>Tomamos el asistente de documentos RAG de <a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/\">parte 5</a>,\nlo envolvemos en una <strong>suite de regresión DeepEval</strong> — en contenedor, juzgada por <code>gemma4</code> en la API de\nInferencia WEC, sin dependencia de OpenAI — y luego usamos esa suite para responder a una pregunta genuinamente abierta:\n<strong>¿deberíamos cambiar el modelo de generación del servicio?</strong> Qwen2.5-3B-Instruct (actual) vs\ngemma4 (candidato), tasa de aprobación y tokens por tarea, medidos cara a cara.</p>\n<p>Spoiler: la evaluación nos salvó de una migración innecesaria. Y en el camino encontramos cuatro fallos reales\nde producción: un espiral mortal por disco lleno, una condición de carrera en el arranque, una trampa de variable de Docker Compose\nque evaluó silenciosamente el <em>modelo incorrecto</em>, y la solución que hace que esa clase de\nerror sea imposible. Cada comando, número y error a continuación proviene de una ejecución real.</p>\n<!-- -->\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"lo-que-estamos-construyendo\">Lo que estamos construyendo<a href=\"https://wec.wiline.com/docs/es/tutorials/deepeval-regression-testing-wiline-inference/#lo-que-estamos-construyendo\" class=\"hash-link\" aria-label=\"Enlace directo al Lo que estamos construyendo\" title=\"Enlace directo al Lo que estamos construyendo\" translate=\"no\">​</a></h2>\n<!-- -->\n<p>Tres decisiones de diseño desde el principio:</p>\n<ol>\n<li class=\"\"><strong>El ejecutor de evaluación es un contenedor</strong>, no un venv. La suite que ejecutas localmente es byte por byte\nel artefacto que ejecuta CI — <code>docker compose run eval</code> en ambos lugares. (Nuestro primer intento fue un\nvenv. Murió por un disco lleno antes de instalar nada; consulta Solución de problemas.)</li>\n<li class=\"\"><strong>El juez es gemma4 en WEC.</strong> DeepEval asume OpenAI por defecto; le damos una clase de modelo personalizada de 30 líneas en su lugar. Las preguntas, respuestas y veredictos nunca salen de nuestra infraestructura.</li>\n<li class=\"\"><strong>Reutilizamos el conjunto dorado de la parte 5 sin cambios.</strong> El <code>tests.csv</code> de promptfoo incrusta cada\nhecho de referencia dentro de una cadena <code>llm-rubric</code> — una expresión regular lo extrae. Tus datos de evaluación\nsobreviven a tu marco de evaluación.</li>\n</ol>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"requisitos-previos\">Requisitos previos<a href=\"https://wec.wiline.com/docs/es/tutorials/deepeval-regression-testing-wiline-inference/#requisitos-previos\" class=\"hash-link\" aria-label=\"Enlace directo al Requisitos previos\" title=\"Enlace directo al Requisitos previos\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\">El <strong>servicio RAG en funcionamiento de <a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/\">parte 5</a></strong>\n(<code>rag-api</code> respondiendo en <code>:8000</code>, <code>eval/tests.csv</code> presente).</li>\n<li class=\"\">Una <strong>clave de API de Inferencia WEC</strong> (<a class=\"\" href=\"https://wec.wiline.com/docs/es/cloud_portal/platform/inference/api_keys/\">Inferencia → Claves de API</a>).</li>\n<li class=\"\"><strong>~2 GB de disco libre.</strong> En serio — verifica <code>df -h /</code> <em>ahora</em>. El nuestro leía 100% y la historia de por qué\nestá en Solución de problemas.</li>\n</ul>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-0--prueba-de-humo-del-sistema-bajo-prueba\">Paso 0 — prueba de humo del sistema bajo prueba<a href=\"https://wec.wiline.com/docs/es/tutorials/deepeval-regression-testing-wiline-inference/#paso-0--prueba-de-humo-del-sistema-bajo-prueba\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 0 — prueba de humo del sistema bajo prueba\" title=\"Enlace directo al Paso 0 — prueba de humo del sistema bajo prueba\" translate=\"no\">​</a></h2>\n<p>Nunca evalúes un servicio que no hayas probado manualmente primero:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-s</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-X</span><span class=\"token plain\"> POST localhost:8000/ask </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Content-Type: application/json\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'{\"question\": \"¿Cómo creo una instancia de cómputo en WEC?\"}'</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> python3 </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-m</span><span class=\"token plain\"> json.tool</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El servicio RAG respondiendo a una pregunta de documentos con fuentes\" src=\"https://wec.wiline.com/docs/es/assets/images/deepeval-smoke-test-98290b8dda1f2a3f5e57a82fa43c2dfa.png\" width=\"900\" height=\"275\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 1.</strong> El sistema bajo prueba, vivo: <code>/ask</code> responde del corpus de documentos de WEC y cita\nsus fuentes. Nunca evalúes un servicio que no hayas probado manualmente primero.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-1--el-ejecutor-de-evaluación-en-contenedor\">Paso 1 — el ejecutor de evaluación en contenedor<a href=\"https://wec.wiline.com/docs/es/tutorials/deepeval-regression-testing-wiline-inference/#paso-1--el-ejecutor-de-evaluaci%C3%B3n-en-contenedor\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 1 — el ejecutor de evaluación en contenedor\" title=\"Enlace directo al Paso 1 — el ejecutor de evaluación en contenedor\" translate=\"no\">​</a></h2>\n<p><code>eval/Dockerfile</code>:</p>\n<div class=\"language-docker codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">eval/Dockerfile</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-docker codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">FROM python:3.12-slim</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">WORKDIR /eval</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">RUN pip install --no-cache-dir deepeval==3.*</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"># El código de la suite se monta en tiempo de ejecución para que las ediciones no necesiten una reconstrucción</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">CMD [\"deepeval\", \"test\", \"run\", \"test_rag_regression.py\"]</span><br></div></code></pre></div></div>\n<p>Agrega el servicio a <code>docker-compose.yml</code> <strong>dentro del bloque <code>services:</code></strong> (el archivo de la parte 5 termina\ncon una sección <code>volumes:</code> de nivel superior — agregar ciegamente coloca tu servicio en el bloque incorrecto;\n<code>docker compose config --quiet</code> lo detecta):</p>\n<div class=\"language-yaml codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">docker-compose.yml (nuevo servicio, dentro de services:)</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-yaml codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">eval</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">build</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> ./eval</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">profiles</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"eval\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">env_file</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> .env</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">environment</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">RAG_API_URL</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> http</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\">//rag</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">api</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token number\" style=\"color:#36acaa\">8000</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">volumes</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\"> ./eval</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\">/eval</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">depends_on</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\"> rag</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">api</span><br></div></code></pre></div></div>\n<p><code>profiles: [\"eval\"]</code> lo mantiene fuera de un <code>docker compose up</code> normal — solo se ejecuta cuando lo pides.\nConstruyelo:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--profile</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">eval</span><span class=\"token plain\"> build </span><span class=\"token builtin class-name\">eval</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"docker compose construyendo la imagen del ejecutor de evaluación\" src=\"https://wec.wiline.com/docs/es/assets/images/deepeval-eval-build-58810296ef048e28edf1a9f90735d2eb.png\" width=\"1619\" height=\"295\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 2.</strong> El ejecutor de evaluación se construye en ~42s: <code>python:3.12-slim</code> más DeepEval 3.x. Esta imagen exacta\nes la que CI ejecuta más tarde — sin venv, sin desviaciones.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-2--un-juez-en-tu-propia-infraestructura\">Paso 2 — un juez en tu propia infraestructura<a href=\"https://wec.wiline.com/docs/es/tutorials/deepeval-regression-testing-wiline-inference/#paso-2--un-juez-en-tu-propia-infraestructura\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 2 — un juez en tu propia infraestructura\" title=\"Enlace directo al Paso 2 — un juez en tu propia infraestructura\" translate=\"no\">​</a></h2>\n<p>Las métricas de LLM-como-juez de DeepEval quieren una clave de OpenAI. No tenemos una y no queremos una.\n<code>eval/wec_judge.py</code>:</p>\n<div class=\"language-python codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">eval/wec_judge.py</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-python codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token triple-quoted-string string\" style=\"color:#e3116c\">\"\"\"Juez personalizado de DeepEval respaldado por WiLine Inference (compatible con OpenAI).\"\"\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> os</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">from</span><span class=\"token plain\"> openai </span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> OpenAI</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">from</span><span class=\"token plain\"> deepeval</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">models</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">base_model </span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> DeepEvalBaseLLM</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">WEC_BASE_URL </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> os</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">getenv</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"WEC_BASE_URL\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"https://inference.wiline.com/v1\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">class</span><span class=\"token plain\"> </span><span class=\"token class-name\">WECJudge</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">DeepEvalBaseLLM</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">__init__</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">self</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> model</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token builtin\">str</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> </span><span class=\"token boolean\" style=\"color:#36acaa\">None</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token boolean\" style=\"color:#36acaa\">None</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        self</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">model_name </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> model </span><span class=\"token keyword\" style=\"color:#00009f\">or</span><span class=\"token plain\"> os</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">getenv</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"JUDGE_MODEL\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"gemma4\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        self</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">client </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> OpenAI</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            base_url</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\">WEC_BASE_URL</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            api_key</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\">os</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">environ</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"WEC_API_KEY\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">load_model</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">self</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:#00009f\">return</span><span class=\"token plain\"> self</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">client</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">generate</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">self</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> prompt</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token builtin\">str</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> </span><span class=\"token builtin\">str</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        response </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> self</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">client</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">chat</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">completions</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">create</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            model</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\">self</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">model_name</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            messages</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"role\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"user\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"content\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> prompt</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            temperature</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token number\" style=\"color:#36acaa\">0</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:#00009f\">return</span><span class=\"token plain\"> response</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">choices</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token number\" style=\"color:#36acaa\">0</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">message</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">content</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">async</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">a_generate</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">self</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> prompt</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token builtin\">str</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> </span><span class=\"token builtin\">str</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:#00009f\">return</span><span class=\"token plain\"> self</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">generate</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">prompt</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">get_model_name</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">self</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:#00009f\">return</span><span class=\"token plain\"> </span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">f\"WEC/</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">self</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">.</span><span class=\"token string-interpolation interpolation\">model_name</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">\"</span><br></div></code></pre></div></div>\n<p>Prueba que responde desde dentro de la red de compose antes de construir nada sobre ello:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--profile</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">eval</span><span class=\"token plain\"> run </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rm</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">eval</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  python </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"from wec_judge import WECJudge; j = WECJudge(); print('el juez dice:', j.generate('Responde exactamente: juez WEC en línea'))\"</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">el juez dice: juez WEC en línea</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Verificación de conectividad del juez desde dentro de la red de compose\" src=\"https://wec.wiline.com/docs/es/assets/images/deepeval-judge-online-c5844beb37c759e19f698f4ea927ec8c.png\" width=\"897\" height=\"170\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 3.</strong> \"juez WEC en línea\" — gemma4 respondiendo a través del contenedor, en la misma red de compose que el servicio que juzgará. Sin clave de OpenAI en ninguna parte de la pila.</p>\n<p>Una advertencia honesta que llevamos a través del resto de este tutorial: en la comparación de modelos más adelante,\n<strong>gemma4 es tanto juez como concursante</strong>. La mitigación es estructural: el juez solo compara\nrespuesta frente a hecho de referencia, nunca \"qué modelo escribió esto\" — y observamos sus veredictos en los\ncasos conocidos difíciles para favoritismo. (No mostró ninguno: cada caso en el que falló gemma4 es un caso en el que también falla Qwen.)</p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Habilidad desbloqueada 🏅</div><div class=\"admonitionContent_BuS1\"><p>Puedes apuntar las métricas juzgadas por LLM de DeepEval a cualquier punto final compatible con OpenAI. Tus veredictos de evaluación — preguntas, respuestas, razonamiento — nunca salen de tu infraestructura.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-3--expón-lo-que-quieres-medir\">Paso 3 — expón lo que quieres medir<a href=\"https://wec.wiline.com/docs/es/tutorials/deepeval-regression-testing-wiline-inference/#paso-3--exp%C3%B3n-lo-que-quieres-medir\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 3 — expón lo que quieres medir\" title=\"Enlace directo al Paso 3 — expón lo que quieres medir\" translate=\"no\">​</a></h2>\n<p>El <code>/ask</code> de la parte 5 devolvió <code>answer</code> y <code>sources</code> — y desechó el uso de tokens que venía con cada finalización. Para la economía de tokens lo necesitamos, y para el A/B necesitamos que el modelo sea intercambiable. Tres variables de entorno y dos campos de respuesta en <code>app/main.py</code> (montado en volumen; uvicorn\nrecarga en caliente, sin reconstrucción):</p>\n<div class=\"language-python codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">app/main.py (extracto)</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-python codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># El modelo de generación es configurable para que la suite de evaluación pueda A/B modelos.</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># Los valores predeterminados preservan la configuración del tutorial-5.</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">GEN_MODEL </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> os</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">getenv</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"GEN_MODEL\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Qwen2.5-3B-Instruct\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">GEN_BASE_URL </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> os</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">getenv</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"GEN_BASE_URL\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"https://inference.wiline.com/v1\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">GEN_API_KEY </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> os</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">getenv</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"GEN_API_KEY\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> os</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">environ</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"WEC_API_KEY\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><br></div></code></pre></div></div>\n<p><code>generate()</code> ahora también devuelve <code>resp.usage</code>, y <code>/ask</code> informa ambos:</p>\n<div class=\"language-json codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida (abreviada)</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-json codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token property\" style=\"color:#36acaa\">\"answer\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Para ver tus estados de cuenta ...\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token property\" style=\"color:#36acaa\">\"sources\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"...\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token property\" style=\"color:#36acaa\">\"model\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Qwen2.5-3B-Instruct\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token property\" style=\"color:#36acaa\">\"usage\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token property\" style=\"color:#36acaa\">\"prompt_tokens\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">1238</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token property\" style=\"color:#36acaa\">\"completion_tokens\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">122</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token property\" style=\"color:#36acaa\">\"total_tokens\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">1360</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"/ask respuesta ahora informando modelo y uso de tokens\" src=\"https://wec.wiline.com/docs/es/assets/images/deepeval-ask-usage-b1a5b036ae1af43fa37d1c91c9d2f092.png\" width=\"1060\" height=\"361\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 4.</strong> Los nuevos campos <code>model</code> y <code>usage</code>: 1,238 tokens de prompt + 122 tokens de finalización para una\npregunta de facturación. Este es el número del debate sobre economía de tokens.</p>\n<p>Ese bloque <code>usage</code> es el número del marketing de GPT-5.6 — medido en tu carga de trabajo,\npor solicitud. Pasa la variable a través de compose (<code>GEN_MODEL: ${GEN_MODEL:-Qwen2.5-3B-Instruct}</code>\nbajo <code>rag-api</code>'s <code>environment:</code>) y recuerda esta línea — vuelve a morder en\nSolución de problemas.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-4--la-suite-de-regresión\">Paso 4 — la suite de regresión<a href=\"https://wec.wiline.com/docs/es/tutorials/deepeval-regression-testing-wiline-inference/#paso-4--la-suite-de-regresi%C3%B3n\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 4 — la suite de regresión\" title=\"Enlace directo al Paso 4 — la suite de regresión\" translate=\"no\">​</a></h2>\n<p><code>eval/test_rag_regression.py</code>. Lee el <code>tests.csv</code> de la parte 5, extrae el hecho de referencia de\ncada rúbrica de promptfoo, pregunta al servicio en vivo, registra el uso de tokens y afirma una métrica de corrección\nde GEval juzgada por <code>WECJudge</code>:</p>\n<div class=\"language-python codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">eval/test_rag_regression.py</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-python codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token triple-quoted-string string\" style=\"color:#e3116c\">\"\"\"Suite de regresión DeepEval para el servicio RAG de documentos WEC.</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token triple-quoted-string string\" style=\"display:inline-block;color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token triple-quoted-string string\" style=\"color:#e3116c\">Reutiliza el conjunto dorado de promptfoo (tests.csv) del tutorial anterior:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token triple-quoted-string string\" style=\"color:#e3116c\">el hecho de referencia se analiza de cada cadena llm-rubric.</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token triple-quoted-string string\" style=\"color:#e3116c\">\"\"\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> csv</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> json</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> os</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> re</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> pytest</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> requests</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">from</span><span class=\"token plain\"> deepeval </span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> assert_test</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">from</span><span class=\"token plain\"> deepeval</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">metrics </span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> GEval</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">from</span><span class=\"token plain\"> deepeval</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">test_case </span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> LLMTestCase</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> LLMTestCaseParams</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">from</span><span class=\"token plain\"> wec_judge </span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> WECJudge</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">RAG_API_URL </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> os</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">getenv</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"RAG_API_URL\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"http://rag-api:8000\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">USAGE_LOG </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> os</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">getenv</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"USAGE_LOG\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"usage_log.jsonl\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">MAX_CASES </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token builtin\">int</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">os</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">getenv</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"MAX_CASES\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"0\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\">  </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># 0 = todos</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">FACT_RE </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> re</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token builtin\">compile</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">r'Hecho de referencia de los documentos oficiales: \"(.*?)\"'</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">load_cases</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">with</span><span class=\"token plain\"> </span><span class=\"token builtin\">open</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"tests.csv\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> newline</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token string\" style=\"color:#e3116c\">\"\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:#00009f\">as</span><span class=\"token plain\"> f</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:#00009f\">for</span><span class=\"token plain\"> row </span><span class=\"token keyword\" style=\"color:#00009f\">in</span><span class=\"token plain\"> csv</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">DictReader</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">f</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            m </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> FACT_RE</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">search</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">row</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"__expected\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token keyword\" style=\"color:#00009f\">yield</span><span class=\"token plain\"> row</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"question\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">m</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">group</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token number\" style=\"color:#36acaa\">1</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:#00009f\">if</span><span class=\"token plain\"> m </span><span class=\"token keyword\" style=\"color:#00009f\">else</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">CASES </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token builtin\">list</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">load_cases</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">if</span><span class=\"token plain\"> MAX_CASES</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    CASES </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> CASES</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\">MAX_CASES</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">judge </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> WECJudge</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">correctness </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> GEval</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    name</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token string\" style=\"color:#e3116c\">\"Correctness\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    criteria</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"La salida real debe abordar correctamente la pregunta de entrada y no debe \"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"contradecir el hecho de referencia dado como salida esperada. Diferente redacción \"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"está bien. Fallar solo si la respuesta es incorrecta, contradice el hecho de referencia, \"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"o no responde a la pregunta.\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    evaluation_params</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        LLMTestCaseParams</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">INPUT</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        LLMTestCaseParams</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">ACTUAL_OUTPUT</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        LLMTestCaseParams</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">EXPECTED_OUTPUT</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    model</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\">judge</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    threshold</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token number\" style=\"color:#36acaa\">0.5</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token decorator annotation punctuation\" style=\"color:#393A34\">@pytest</span><span class=\"token decorator annotation punctuation\" style=\"color:#393A34\">.</span><span class=\"token decorator annotation punctuation\" style=\"color:#393A34\">mark</span><span class=\"token decorator annotation punctuation\" style=\"color:#393A34\">.</span><span class=\"token decorator annotation punctuation\" style=\"color:#393A34\">parametrize</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token string\" style=\"color:#e3116c\">\"question,reference\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> CASES</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> ids</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token plain\">q</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token number\" style=\"color:#36acaa\">50</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:#00009f\">for</span><span class=\"token plain\"> q</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> _ </span><span class=\"token keyword\" style=\"color:#00009f\">in</span><span class=\"token plain\"> CASES</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">test_rag_regression</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">question</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> reference</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    r </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> requests</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">post</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">f\"</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">RAG_API_URL</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">/ask\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> json</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"question\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> question</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> timeout</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token number\" style=\"color:#36acaa\">120</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    r</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">raise_for_status</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    data </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> r</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">json</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    expect </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> os</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">getenv</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"EXPECT_MODEL\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">assert</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:#00009f\">not</span><span class=\"token plain\"> expect </span><span class=\"token keyword\" style=\"color:#00009f\">or</span><span class=\"token plain\"> data</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"model\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">==</span><span class=\"token plain\"> expect</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">f\"la suite esperaba </span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">expect</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\"> pero el servicio está ejecutando </span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">data</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">[</span><span class=\"token string-interpolation interpolation string\" style=\"color:#e3116c\">'model'</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">]</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">with</span><span class=\"token plain\"> </span><span class=\"token builtin\">open</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">USAGE_LOG</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"a\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:#00009f\">as</span><span class=\"token plain\"> f</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        f</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">write</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">json</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">dumps</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string\" style=\"color:#e3116c\">\"model\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> data</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"model\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string\" style=\"color:#e3116c\">\"question\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> question</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token operator\" style=\"color:#393A34\">**</span><span class=\"token plain\">data</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"usage\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">+</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"\\n\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    assert_test</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        LLMTestCase</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token builtin\">input</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\">question</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            actual_output</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\">data</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"answer\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            expected_output</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\">reference</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token plain\">correctness</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><br></div></code></pre></div></div>\n<p>(Esa afirmación <code>EXPECT_MODEL</code> no estaba en la primera versión. Existe debido a un error que encontrarás en un momento.)</p>\n<p>Primera ejecución, limitada a 5 casos para fallar rápido:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--profile</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">eval</span><span class=\"token plain\"> run </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rm</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-e</span><span class=\"token plain\"> </span><span class=\"token assign-left variable\" style=\"color:#36acaa\">MAX_CASES</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token number\" style=\"color:#36acaa\">5</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-e</span><span class=\"token plain\"> </span><span class=\"token assign-left variable\" style=\"color:#36acaa\">DEEPEVAL_TELEMETRY_OPT_OUT</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\">YES </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token builtin class-name\">eval</span><br></div></code></pre></div></div>\n<p><strong>4 de 5 pasaron en 2:37</strong> — y tanto los pases como la falla valen la pena leer. La\nfalla (\"¿Cuál es el punto de partida para la facturación?\", puntuación 0.3) no es un error del servicio: la\nrespuesta era factualmente correcta, pero la etiqueta dorada es un <em>título</em> de sección (\"Cómo Funciona la Facturación\")\ny el juez quería que se nombrara. Recuerda este caso — parpadea entre pasar y fallar durante todo el tutorial, y ese parpadeo se convierte en un hallazgo. Mientras tanto, en \"número total de eventos\"\nel juez puntuó un 0.6, un pase límite con el razonamiento exactamente correcto (el servicio\ndescribió la característica en lugar de dar pasos). Veredictos matizados de un juez autoalojado —\nla cosa que la gente asume que necesitas modelos de clase GPT-4 para.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Primera ejecución de DeepEval: 4 de 5 pasaron, con razonamiento del juez\" src=\"https://wec.wiline.com/docs/es/assets/images/deepeval-first-run-cabfff276f20b525d0c7c20bb52c1a6a.png\" width=\"1058\" height=\"984\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 5.</strong> Primera ejecución: 4 de 5 pasaron en 2:37, cada veredicto con razonamiento escrito del\njuez gemma4 — incluyendo la única falla, un veredicto estricto sobre una etiqueta de título de sección\nen lugar de un error del servicio.</p>\n<p>Nota la línea de resumen de DeepEval: <code>costo de token: Ninguno</code>. No puede calcular el precio de un juez autoalojado — que es\nexactamente por qué la suite escribe su propio <code>usage_log.jsonl</code>.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Registro de tokens por pregunta escrito por la suite\" src=\"https://wec.wiline.com/docs/es/assets/images/deepeval-usage-log-65042f9bd05652b9ff3814cd52b37270.png\" width=\"897\" height=\"575\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 6.</strong> La contabilidad propia de la suite: una línea JSON por pregunta — modelo, prompt, finalización,\ntotal — los datos en bruto de cada comparación en este tutorial se construyen a partir de esto.</p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Habilidad desbloqueada 🏅</div><div class=\"admonitionContent_BuS1\"><p>Tienes una suite de regresión que califica un servicio RAG en vivo — reutilizando el conjunto dorado del último tutorial\nsin cambios, juzgada por un modelo autoalojado, con contabilidad de tokens por pregunta que el marco\nmismo no puede proporcionarte.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-5--el-experimento-qwen25-3b-vs-gemma4\">Paso 5 — el experimento: Qwen2.5-3B vs gemma4<a href=\"https://wec.wiline.com/docs/es/tutorials/deepeval-regression-testing-wiline-inference/#paso-5--el-experimento-qwen25-3b-vs-gemma4\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 5 — el experimento: Qwen2.5-3B vs gemma4\" title=\"Enlace directo al Paso 5 — el experimento: Qwen2.5-3B vs gemma4\" translate=\"no\">​</a></h2>\n<p>La pregunta que la suite existe para responder: nuestro servicio genera con Qwen2.5-3B-Instruct —\n¿debería hacerlo? gemma4 es más nuevo, bien considerado, ya está en el catálogo de WEC. Se siente como una mejora.\n<em>Se siente.</em> Vamos a medir.</p>\n<p>Línea base, todos los 16 casos:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--profile</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">eval</span><span class=\"token plain\"> run </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rm</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-e</span><span class=\"token plain\"> </span><span class=\"token assign-left variable\" style=\"color:#36acaa\">DEEPEVAL_TELEMETRY_OPT_OUT</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\">YES </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-e</span><span class=\"token plain\"> </span><span class=\"token assign-left variable\" style=\"color:#36acaa\">USAGE_LOG</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\">usage_qwen.jsonl </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token builtin class-name\">eval</span><br></div></code></pre></div></div>\n<p><strong>14/16 (87.5%) en 8:50.</strong> Las dos fallas son artefactos de etiqueta, no errores de RAG: los hechos dorados\nde la parte 5 son <em>títulos</em> de sección (\"Cómo Funciona la Facturación\"), y el juez penalizó respuestas que eran\nfactualmente correctas pero no repetían el título; y <code>mp3</code> frente a la etiqueta <code>audio/mpeg</code> — el mismo hecho en\ndos niveles de abstracción, dictaminado como una contradicción. Lee tus fallas antes de confiar en tu tasa de aprobación. Dejamos ambas: son una deuda de etiqueta honesta, y se convierten en nuestro canario de favoritismo del juez en el A/B.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Línea base de Qwen: 14 de 16 pasaron\" src=\"https://wec.wiline.com/docs/es/assets/images/deepeval-qwen-baseline-5b61265199e0d615241bbbfdb30d7a81.png\" width=\"1890\" height=\"977\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 7.</strong> La línea base: 87.5% en 8:50. Ambas fallas son artefactos de etiqueta, no errores de RAG —\nlee tus fallas antes de confiar en tu tasa de aprobación.</p>\n<p>Desafiante — misma suite, una variable de entorno (prefíjela en <strong>cada</strong> comando de compose; Solución de problemas\nexplica por qué en sangre):</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token assign-left variable\" style=\"color:#36acaa\">GEN_MODEL</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\">gemma4 </span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose up </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> rag-api</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">until</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-sf</span><span class=\"token plain\"> localhost:8000/health </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> /dev/null</span><span class=\"token punctuation\" style=\"color:#393A34\">;</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:#00009f\">do</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">echo</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"esperando...\"</span><span class=\"token punctuation\" style=\"color:#393A34\">;</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">sleep</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">3</span><span class=\"token punctuation\" style=\"color:#393A34\">;</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:#00009f\">done</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-s</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-X</span><span class=\"token plain\"> POST localhost:8000/ask </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Content-Type: application/json\"</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'{\"question\": \"ping\"}'</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> python3 </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"import json,sys; print('&gt;&gt;&gt; el modelo es:', json.load(sys.stdin)['model'])\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token assign-left variable\" style=\"color:#36acaa\">GEN_MODEL</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\">gemma4 </span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--profile</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">eval</span><span class=\"token plain\"> run </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rm</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-e</span><span class=\"token plain\"> </span><span class=\"token assign-left variable\" style=\"color:#36acaa\">DEEPEVAL_TELEMETRY_OPT_OUT</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\">YES </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-e</span><span class=\"token plain\"> </span><span class=\"token assign-left variable\" style=\"color:#36acaa\">USAGE_LOG</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\">usage_gemma4.jsonl </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-e</span><span class=\"token plain\"> </span><span class=\"token assign-left variable\" style=\"color:#36acaa\">EXPECT_MODEL</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\">gemma4 </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token builtin class-name\">eval</span><br></div></code></pre></div></div>\n<p>Y verifica el registro, porque las etiquetas de ejecución mienten:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">sort</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&lt;</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token function\" style=\"color:#d73a49\">cat</span><span class=\"token plain\"> eval/usage_gemma4.jsonl </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> python3 </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"import json,sys; [print(json.loads(l)['model']) for l in sys.stdin]\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">uniq</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\">#     16 gemma4</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Cambiando el servicio a gemma4 y verificando desde la respuesta\" src=\"https://wec.wiline.com/docs/es/assets/images/deepeval-gemma4-run-6a7c64183ec8b68e6d5e5ff76dd0924a.png\" width=\"744\" height=\"196\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 8.</strong> Cambiando el desafiante: <code>GEN_MODEL=gemma4</code>, espera por <code>/health</code>, luego confirma\n<code>&gt;&gt;&gt; el modelo es: gemma4</code> desde la propia respuesta — antes de gastar un solo minuto de evaluación.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Resumen de ejecución de gemma4: 13 de 16 pasaron\" src=\"https://wec.wiline.com/docs/es/assets/images/deepeval-gemma4-verified-8d7291e9dfcda2f337f83ca7e4988071.png\" width=\"1890\" height=\"981\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 9.</strong> La ejecución de gemma4: 81.25% en ~13 minutos — y sus tres fallas son los mismos\ncasos de artefactos de etiqueta en los que Qwen parpadea. Sin favoritismo del juez gemma4 hacia el concursante gemma4.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"la-tabla-de-veredictos\">La tabla de veredictos<a href=\"https://wec.wiline.com/docs/es/tutorials/deepeval-regression-testing-wiline-inference/#la-tabla-de-veredictos\" class=\"hash-link\" aria-label=\"Enlace directo al La tabla de veredictos\" title=\"Enlace directo al La tabla de veredictos\" translate=\"no\">​</a></h3>\n<p>Un script sobre los registros de uso:</p>\n<table><thead><tr><th>ejecución</th><th>tasa de aprobación</th><th>tokens promedio de prompt</th><th>tokens promedio de finalización</th><th>tokens totales</th></tr></thead><tbody><tr><td>Qwen2.5-3B (ejecución 1)</td><td>87.5%</td><td>1,126</td><td><strong>75</strong></td><td>19,225</td></tr><tr><td>Qwen2.5-3B (ejecución 2)</td><td>81.25%</td><td>1,126</td><td><strong>75</strong></td><td>19,225</td></tr><tr><td>gemma4</td><td>81.25%</td><td>1,199</td><td><strong>415</strong></td><td>25,834</td></tr></tbody></table>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Tabla de comparación de tokens: Qwen vs gemma4\" src=\"https://wec.wiline.com/docs/es/assets/images/deepeval-verdict-table-fc9c10d332e08f09d63c768e48c1b23c.png\" width=\"752\" height=\"408\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 10.</strong> El veredicto en cinco líneas: misma tasa de aprobación, 5.5× los tokens de finalización. La evaluación\njusto previno una migración innecesaria.</p>\n<p>Dos hallazgos, uno esperado y uno no:</p>\n<p><strong>El veredicto: no cambies.</strong> Misma calidad efectiva, mismos casos de falla — pero gemma4 usa\n<strong>5.5× los tokens de finalización</strong> (415 frente a 75 por respuesta) y se ejecuta ~45% más lento por pregunta. En una\ncarga de trabajo RAG donde el contexto recuperado de ~1,100 tokens domina el prompt, la columna de finalización es toda la diferencia de costo. \"Modelo más nuevo\" perdió ante \"mídelo\" en una tarde. Esta es precisamente la evaluación que el post de noticias de GPT-5.6 dijo que ejecutarías — y es la misma evaluación que ejecutarías\ncontra GPT-5.6 mismo: cambia <code>GEN_MODEL</code>, <code>GEN_BASE_URL</code>, y <code>GEN_API_KEY</code>, nada más.</p>\n<p><strong>El juez tiene un umbral de ruido.</strong> Mira las dos filas de Qwen: <strong>cuentas de tokens idénticas</strong> —\n19,225 en ambas ejecuciones, porque la generación con temperatura 0 es determinista — sin embargo 87.5% frente a 81.25%.\nLas respuestas no cambiaron; solo los veredictos del juez lo hicieron (un límite de 0.6 se convirtió en un 0.3).\nEso aísla limpiamente la no determinación del juez del comportamiento del modelo, y establece tu umbral de CI:\nuna puerta en \"≥ 87.5%\" fallaría. El nuestro tolera un veredicto cambiado y falla en dos o más\nregresiones reales.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Ejecución 2 de Qwen: respuestas idénticas, veredictos diferentes\" src=\"https://wec.wiline.com/docs/es/assets/images/deepeval-qwen-run2-48156c484cd782d1e346e0938b1080b9.png\" width=\"1055\" height=\"981\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 11.</strong> Ejecución 2 de Qwen: las mismas 16 respuestas deterministas, 81.25% esta vez — el juez, no\nel modelo, es donde vive la variación.</p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Habilidad desbloqueada 🏅</div><div class=\"admonitionContent_BuS1\"><p>Puedes resolver un debate sobre el cambio de modelo con datos medidos — tasa de aprobación <em>y</em> tokens por tarea — y\nconoces el umbral de ruido de tu juez, así que puedes establecer un umbral de CI que no falle.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-6--la-puerta-de-ci\">Paso 6 — la puerta de CI<a href=\"https://wec.wiline.com/docs/es/tutorials/deepeval-regression-testing-wiline-inference/#paso-6--la-puerta-de-ci\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 6 — la puerta de CI\" title=\"Enlace directo al Paso 6 — la puerta de CI\" translate=\"no\">​</a></h2>\n<p>La recompensa de contenerizar todo: el trabajo de CI es el comando que has estado ejecutando todo\nel tiempo. <code>.github/workflows/eval.yml</code>:</p>\n<div class=\"language-yaml codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">.github/workflows/eval.yml</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-yaml codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token key atrule\" style=\"color:#00a4db\">name</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> rag</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">regression</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">eval</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token key atrule\" style=\"color:#00a4db\">on</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">pull_request</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">paths</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"app/**\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"eval/**\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">workflow_dispatch</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token key atrule\" style=\"color:#00a4db\">jobs</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">eval</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">runs-on</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token plain\">self</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">hosted</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> wec</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\">   </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># corredor con acceso a inference.wiline.com</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">steps</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\"> </span><span class=\"token key atrule\" style=\"color:#00a4db\">uses</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> actions/checkout@v4</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\"> </span><span class=\"token key atrule\" style=\"color:#00a4db\">name</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> Ejecutar suite de regresión</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token key atrule\" style=\"color:#00a4db\">env</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">          </span><span class=\"token key atrule\" style=\"color:#00a4db\">WEC_API_KEY</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> $</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"> secrets.WEC_API_KEY </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token key atrule\" style=\"color:#00a4db\">run</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">|</span><span class=\"token scalar string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token scalar string\" style=\"color:#e3116c\">          docker compose up -d rag-api</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token scalar string\" style=\"color:#e3116c\">          docker compose --profile eval run --rm \\</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token scalar string\" style=\"color:#e3116c\">            -e DEEPEVAL_TELEMETRY_OPT_OUT=YES \\</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token scalar string\" style=\"color:#e3116c\">            -e EXPECT_MODEL=Qwen2.5-3B-Instruct \\</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token scalar string\" style=\"color:#e3116c\">            eval</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\"> </span><span class=\"token key atrule\" style=\"color:#00a4db\">name</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> Desmontar</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token key atrule\" style=\"color:#00a4db\">if</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> always()</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token key atrule\" style=\"color:#00a4db\">run</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> docker compose down</span><br></div></code></pre></div></div>\n<p>Cualquier PR que toque la aplicación o la evaluación debe sobrevivir a 16 preguntas juzgadas contra el servicio en vivo — con la identidad del modelo afirmada — antes de que se fusione.</p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Habilidad desbloqueada 🏅</div><div class=\"admonitionContent_BuS1\"><p>Tu evaluación es una puerta de CI. El contenedor que ejecutaste localmente es byte por byte lo que CI ejecuta — sin\n\"funciona en mi máquina\" brecha entre desarrollo y la tubería.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"solución-de-problemas--las-cuatro-fallas-reales\">Solución de problemas — las cuatro fallas reales<a href=\"https://wec.wiline.com/docs/es/tutorials/deepeval-regression-testing-wiline-inference/#soluci%C3%B3n-de-problemas--las-cuatro-fallas-reales\" class=\"hash-link\" aria-label=\"Enlace directo al Solución de problemas — las cuatro fallas reales\" title=\"Enlace directo al Solución de problemas — las cuatro fallas reales\" translate=\"no\">​</a></h2>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"errno-28-no-hay-espacio-en-el-dispositivo--y-volvió\"><code>[Errno 28] No hay espacio en el dispositivo</code> — y volvió<a href=\"https://wec.wiline.com/docs/es/tutorials/deepeval-regression-testing-wiline-inference/#errno-28-no-hay-espacio-en-el-dispositivo--y-volvi%C3%B3\" class=\"hash-link\" aria-label=\"Enlace directo al errno-28-no-hay-espacio-en-el-dispositivo--y-volvió\" title=\"Enlace directo al errno-28-no-hay-espacio-en-el-dispositivo--y-volvió\" translate=\"no\">​</a></h3>\n<p>Nuestra primera <code>pip install deepeval</code> falló: disco 100% lleno, 58G de 58G. Liberamos 2 GB de registros de diario\ny caché de construcción… y estaba lleno de nuevo <em>minutos después</em>. El culpable:</p>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida (el culpable)</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">7.9G  /var/lib/docker/containers/10281d.../10281d...-json.log</span><br></div></code></pre></div></div>\n<p><code>langfuse-clickhouse-1</code> estaba en un bucle de error, rociando trazas de pila en un registro json de Docker con\n<strong>sin rotación configurada</strong> — un espiral mortal, porque un disco lleno causa más errores que causa más\nregistros. Había estado \"no saludable\" durante 8 días. La solución, en orden (el orden importa — truncar mientras\nel contenedor se ejecuta simplemente vuelve a llenar):</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> stop langfuse-clickhouse-1</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">sudo</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">sh</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"truncate -s 0 /var/lib/docker/containers/10281d*/*-json.log\"</span><br></div></code></pre></div></div>\n<p>Luego hazlo permanente — rotación de registros en la sobrecarga de compose, aplicada a cada servicio:</p>\n<div class=\"language-yaml codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">docker-compose.override.yml (pila de Langfuse)</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-yaml codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token key atrule\" style=\"color:#00a4db\">x-logging</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token important\">&amp;default-logging</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">logging</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">driver</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> json</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">file</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">options</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">max-size</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"50m\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">max-file</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"3\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token key atrule\" style=\"color:#00a4db\">services</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">clickhouse</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">&lt;&lt;</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token important\">*default-logging</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># ... cada otro servicio</span><br></div></code></pre></div></div>\n<p><code>docker compose up -d</code> para recrear. ClickHouse se volvió saludable por primera vez en 8 días.\nEl controlador json-file de Docker <strong>no</strong> rota por defecto; en una caja que ejecuta infraestructura LLM\nque registra entusiastamente, los registros de contenedor sin límites son una bomba de tiempo.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"13-pruebas-fallan-con-conexión-rechazada-luego-3-pasan\">13 pruebas fallan con <code>Conexión rechazada</code>, luego 3 pasan<a href=\"https://wec.wiline.com/docs/es/tutorials/deepeval-regression-testing-wiline-inference/#13-pruebas-fallan-con-conexi%C3%B3n-rechazada-luego-3-pasan\" class=\"hash-link\" aria-label=\"Enlace directo al 13-pruebas-fallan-con-conexión-rechazada-luego-3-pasan\" title=\"Enlace directo al 13-pruebas-fallan-con-conexión-rechazada-luego-3-pasan\" translate=\"no\">​</a></h3>\n<p>Cambiar <code>GEN_MODEL</code> recrea <code>rag-api</code>, y la aplicación tarda ~3 minutos en arrancar (Chroma +\ncarga del modelo de incrustación). Nuestra suite comenzó a golpear <code>/ask</code> inmediatamente: 13 fallas\nde conexión rechazadas seguidas, luego la API cobró vida a mitad de ejecución y las últimas 3 pasaron. Flaky de la peor manera — la\nfalla depende del tiempo de reloj de pared.</p>\n<p>Soluciona esto en la suite, no en la disciplina humana — <code>eval/conftest.py</code>:</p>\n<div class=\"language-python codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">eval/conftest.py</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-python codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token triple-quoted-string string\" style=\"color:#e3116c\">\"\"\"Rechaza iniciar la suite hasta que la API RAG esté saludable.\"\"\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> os</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> time</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> requests</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">RAG_API_URL </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> os</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">getenv</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"RAG_API_URL\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"http://rag-api:8000\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">READY_TIMEOUT </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token builtin\">int</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">os</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">getenv</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"READY_TIMEOUT\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"300\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">pytest_configure</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">config</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    deadline </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> time</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">time</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">+</span><span class=\"token plain\"> READY_TIMEOUT</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">while</span><span class=\"token plain\"> time</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">time</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&lt;</span><span class=\"token plain\"> deadline</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:#00009f\">try</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            requests</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">get</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">f\"</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">RAG_API_URL</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">/health\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> timeout</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token number\" style=\"color:#36acaa\">3</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">raise_for_status</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token keyword\" style=\"color:#00009f\">return</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token keyword\" style=\"color:#00009f\">except</span><span class=\"token plain\"> requests</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">RequestException</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            time</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">sleep</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token number\" style=\"color:#36acaa\">3</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">raise</span><span class=\"token plain\"> RuntimeError</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">f\"rag-api no saludable después de </span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">READY_TIMEOUT</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">s — abortando evaluación\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><br></div></code></pre></div></div>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"la-trampa-de-variable-de-compose-evaluamos-el-modelo-incorrecto-dos-veces\">La trampa de variable de compose: evaluamos el modelo incorrecto. Dos veces.<a href=\"https://wec.wiline.com/docs/es/tutorials/deepeval-regression-testing-wiline-inference/#la-trampa-de-variable-de-compose-evaluamos-el-modelo-incorrecto-dos-veces\" class=\"hash-link\" aria-label=\"Enlace directo al La trampa de variable de compose: evaluamos el modelo incorrecto. Dos veces.\" title=\"Enlace directo al La trampa de variable de compose: evaluamos el modelo incorrecto. Dos veces.\" translate=\"no\">​</a></h3>\n<p>La más desagradable, porque nada falló. <code>${GEN_MODEL:-Qwen…}</code> en compose se interpola\ndesde <em>tu shell</em> en <em>cada</em> invocación. Comenzamos el servicio con\n<code>GEN_MODEL=gemma4 docker compose up -d</code> — correcto. Luego ejecutamos la evaluación <strong>sin</strong> el prefijo.\nCompose resolvió la variable de nuevo al valor predeterminado de Qwen, vio el contenedor en ejecución como un desvío de configuración\ny — a través de <code>depends_on</code> — <strong>silenciosamente recreó <code>rag-api</code> con Qwen</strong>. La ejecución se completó,\nel resumen decía \"gemma4\" en cada veredicto (el <em>juez</em> era gemma4), y cada respuesta había sido\nescrita por Qwen.</p>\n<p>Solo lo atrapamos porque la suite registra el modelo <em>desde la respuesta</em>:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">sort</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&lt;</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">..</span><span class=\"token plain\">. usage_gemma4.jsonl </span><span class=\"token punctuation\" style=\"color:#393A34\">..</span><span class=\"token plain\">.</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">uniq</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\">#     16 Qwen2.5-3B-Instruct     ← la ejecución \"gemma4\"</span><br></div></code></pre></div></div>\n<p>Nunca confíes en la etiqueta de una ejecución; verifica desde los datos que el sistema bajo prueba informa sobre sí mismo.\nLa solución permanente es la afirmación <code>EXPECT_MODEL</code> en la suite (Paso 4). Prueba que funciona — la\nprueba de fallo deliberado:</p>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">FAILED ... AssertionError: la suite esperaba gemma4 pero el servicio está ejecutando Qwen2.5-3B-Instruct</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">1 falló en 2.44s</span><br></div></code></pre></div></div>\n<p>Menos de tres segundos, antes de gastar una sola llamada de juez — frente a los 10+ minutos que cada ejecución mal etiquetada\nnos costó.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Guardia EXPECT_MODEL fallando ruidosamente en el modelo incorrecto\" src=\"https://wec.wiline.com/docs/es/assets/images/deepeval-expect-model-ccc34be3d936ecfd93823c565073b405.png\" width=\"1058\" height=\"729\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 12.</strong> La guardia en acción: modelo incorrecto → fallo ruidoso en menos de tres segundos, antes de que se gaste una\nsola llamada de juez. Compara con los 10+ minutos que cada ejecución mal etiquetada nos costó.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"sleep-5-no-es-una-verificación-de-disponibilidad\"><code>sleep 5</code> no es una verificación de disponibilidad<a href=\"https://wec.wiline.com/docs/es/tutorials/deepeval-regression-testing-wiline-inference/#sleep-5-no-es-una-verificaci%C3%B3n-de-disponibilidad\" class=\"hash-link\" aria-label=\"Enlace directo al sleep-5-no-es-una-verificación-de-disponibilidad\" title=\"Enlace directo al sleep-5-no-es-una-verificación-de-disponibilidad\" translate=\"no\">​</a></h3>\n<p>Pequeño bono: después de recrear el contenedor, canalizamos la salida de <code>/ask</code> a un analizador JSON 5\nsegundos después y obtuvimos <code>json.decoder.JSONDecodeError: Esperando valor: línea 1 columna 1</code> — el\nservidor no estaba arriba, curl devolvió vacío, el analizador se ahogó en nada. Sondea <code>/health</code> en un bucle;\nnunca duermas un número adivinado.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"lo-que-construiste\">Lo que construiste<a href=\"https://wec.wiline.com/docs/es/tutorials/deepeval-regression-testing-wiline-inference/#lo-que-construiste\" class=\"hash-link\" aria-label=\"Enlace directo al Lo que construiste\" title=\"Enlace directo al Lo que construiste\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\">Una <strong>suite de regresión DeepEval en contenedor</strong> — misma imagen localmente y en CI</li>\n<li class=\"\">Un <strong>juez personalizado en WEC Inference</strong> (<code>gemma4</code> + GEval): veredictos con razonamiento, sin clave de OpenAI</li>\n<li class=\"\"><strong>Contabilidad de tokens por pregunta</strong> que el marco mismo no podía darte</li>\n<li class=\"\">Una <strong>decisión de modelo medida</strong>: gemma4 ≈ Qwen en calidad, 5.5× los tokens de finalización — mantén Qwen</li>\n<li class=\"\">Una <strong>medición del umbral de ruido del juez</strong> (respuestas deterministas, un cambio de tasa de aprobación de 6.25 puntos) que establece tu umbral de CI de manera honesta</li>\n<li class=\"\">Una <strong>guardia <code>EXPECT_MODEL</code></strong> que convierte la evaluación silenciosa del modelo incorrecto en un fallo ruidoso en menos de tres segundos</li>\n<li class=\"\">Una <strong>puerta de GitHub Actions</strong> que ejecuta el contenedor idéntico en cada PR</li>\n</ul>\n<div class=\"skillComplete\"><span class=\"skillComplete__medal\" aria-hidden=\"true\">🎯</span><div class=\"skillComplete__text\"><div class=\"skillComplete__title\">Finished this tutorial?</div><div class=\"skillComplete__sub\">Mark it complete to earn <strong>Catch regressions in CI</strong> on your skill path.</div></div><button type=\"button\" class=\"skillComplete__btn\">Mark as complete ✓</button></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"qué-sigue\">Qué sigue<a href=\"https://wec.wiline.com/docs/es/tutorials/deepeval-regression-testing-wiline-inference/#qu%C3%A9-sigue\" class=\"hash-link\" aria-label=\"Enlace directo al Qué sigue\" title=\"Enlace directo al Qué sigue\" translate=\"no\">​</a></h2>\n<p>La suite juzga <em>respuestas</em>. Las trazas de Langfuse de la parte 4 saben <em>por qué</em> — qué fragmentos fueron recuperados,\ncuánto costó el rango de generación, dónde se oculta la latencia. Conectar fallas de evaluación de vuelta a sus trazas\n(ID de prueba de DeepEval ↔ ID de traza de Langfuse) convierte \"la prueba falló\" en \"aquí está la recuperación que\nlo causó.\" Esa es la historia de depuración a nivel de componente, y es hacia donde va esta serie a continuación.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"lectura-adicional\">Lectura adicional<a href=\"https://wec.wiline.com/docs/es/tutorials/deepeval-regression-testing-wiline-inference/#lectura-adicional\" class=\"hash-link\" aria-label=\"Enlace directo al Lectura adicional\" title=\"Enlace directo al Lectura adicional\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\"><a href=\"https://deepeval.com/docs/models-custom\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Documentación de DeepEval — modelos personalizados</a></li>\n<li class=\"\"><a href=\"https://deepeval.com/docs/metrics-llm-evals\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">DeepEval — métrica GEval</a></li>\n<li class=\"\"><a href=\"https://docs.docker.com/engine/logging/drivers/json-file/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Controladores de registro de Docker — rotación de json-file</a></li>\n<li class=\"\"><a href=\"https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Interpolación de variables de Compose</a></li>\n</ul>",
            "url": "https://wec.wiline.com/docs/es/tutorials/deepeval-regression-testing-wiline-inference/",
            "title": "Prueba de regresión de tu servicio RAG con DeepEval — y resuelve un debate sobre modelos con datos",
            "summary": "Envuelve el asistente RAG de la parte 5 en una suite de DeepEval en contenedor juzgada por gemma4 en la API de Inferencia WEC — sin clave de OpenAI en ninguna parte. Luego úsalo para responder a una pregunta real: ¿deberíamos cambiar nuestro modelo de generación? La misma calidad, 5.5× los tokens: la evaluación dice que no. Cada comando, número y error es real.",
            "date_modified": "2026-07-20T00:00:00.000Z",
            "author": {
                "name": "Rafael Fernandes",
                "url": "https://www.linkedin.com/in/rafaelmacariofernandes/"
            },
            "tags": [
                "ai",
                "evals",
                "deepeval",
                "rag",
                "docker",
                "ci",
                "inference"
            ]
        },
        {
            "id": "https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/",
            "content_html": "<div class=\"tutorialHero\"><svg viewBox=\"0 0 24 24\" fill=\"#2496ED\" aria-label=\"Docker\" class=\"tutorialHero__docker\"><path d=\"M13.983 11.078h2.119a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.119a.185.185 0 00-.185.185v1.888c0 .102.083.185.185.185m-2.954-5.43h2.118a.186.186 0 00.186-.186V3.574a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m0 2.716h2.118a.187.187 0 00.186-.186V6.29a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.887c0 .102.082.185.185.186m-2.93 0h2.12a.186.186 0 00.184-.186V6.29a.185.185 0 00-.185-.185H8.1a.185.185 0 00-.185.185v1.887c0 .102.083.185.185.186m-2.964 0h2.119a.186.186 0 00.185-.186V6.29a.185.185 0 00-.185-.185H5.136a.186.186 0 00-.186.185v1.887c0 .102.084.185.186.186m5.893 2.715h2.118a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m-2.93 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.083.185.185.185m-2.964 0h2.119a.185.185 0 00.185-.185V9.006a.185.185 0 00-.184-.186h-2.12a.186.186 0 00-.186.186v1.887c0 .102.084.185.186.185m-2.92 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.082.185.185.185M23.763 9.89c-.065-.051-.672-.51-1.954-.51-.338.001-.676.03-1.01.087-.248-1.7-1.653-2.53-1.716-2.566l-.344-.199-.226.327c-.284.438-.49.922-.612 1.43-.23.97-.09 1.882.403 2.661-.595.332-1.55.413-1.744.42H.751a.751.751 0 00-.75.748 11.376 11.376 0 00.692 4.062c.545 1.428 1.355 2.48 2.41 3.124 1.18.723 3.1 1.137 5.275 1.137.983.003 1.963-.086 2.93-.266a12.248 12.248 0 003.823-1.389c.98-.567 1.86-1.288 2.61-2.136 1.252-1.418 1.998-2.997 2.553-4.4h.221c1.372 0 2.215-.549 2.68-1.009.309-.293.55-.65.707-1.046l.098-.288Z\"></path></svg><span class=\"tutorialHero__plus\">+</span><img class=\"tutorialHero__brand\" src=\"https://wec.wiline.com/docs/es/assets/images/promptfoo-avatar-31b32c657d72dac0a7edbd77c24ec717.jpg\" alt=\"Promptfoo\"><span class=\"tutorialHero__plus\">+</span><img class=\"tutorialHero__langfuse\" src=\"data:image/webp;base64,UklGRq4lAABXRUJQVlA4WAoAAAAQAAAA8wEAWAAAQUxQSFAWAAABsIZt/yKn1W9mPe4e3EoJ1mAnSHFLcKnbqQAt1A3rKS5tobhEcMrBoRSHIkFLqniJkCDxbFjfnf/vw3925r+zmwW+RcQEgDJVQdGJyQlRIYmdMl54ceTgLoka8DTHcRxw2tDouLjoEC0HTx05j3Gahhkfz8/csm3rmkWbzt64W5x/7dyGd1pEhYfqVRwTTh3Rquew114dOeilKT9mbdyYtXjyC62D+KcLUQ04D6lbfLDnepnZSZA4rC4Ud949uCFr5dy32wbI4wKe+2DtiT9vFRXdvl5UbRcIcdmq808uHN0y0qDhnhp0/TDCMxFj9jx0IUvBYbcaS05+2lQtQ9/2q1MPbCifmPJPbZ77VmqY6inB8JOD1OzU0a2/vOZAT5LS7Od4KXz8y4cqBWRMHOaKS/O6BHHsAiL5J2dDH+zqrGbExaZ/d6qCoIeN6zvXi9WL8BFpiwpciIQRLZhyP26uZqR/dsJXQU/OMkot2zupmWjbL7xhEdDzVcc3Lh3fNQqC246Zl2tG9AwhQtnWXnoWXNzr+ypyI56gPUTzljY8A33/3UaCyiRC+YU5GZ8evmsjiEjQ0/bT49sHczI4XcrcfIGcCX+CVopoXN2Ek6Xtc9yGSjbeKCWoVGfJ8a9bqiUZUj/aV4lITj9BG1KGiOVz42W13GlDxROlIApl23to3akaTDpV5sQnbMPKERHvfhLJSYv9tgIVT1DJloM9NWLqtsuKBaTJqSdoGWUUuT2zW7zKnabFNwVEeQq37mvHUarnNhtR/IlaeimFpDZvRd9gMUPfreUEvZYoBGvnRwIA1yyrFt2d9gSnZsg92XkogoiOixPiKf3QUzb0nYQZ3v6qpRriZ1egMuqv271L7s40f4oLqv9c7yEjRg4b3LdH5zZNAv2BUnco3JnVmAPtgLMCeiFhRIgHhNLtA5/5uogopHUZyiYv+E+RGYtOFVRZnC7B5bCaairujfAHHooRgoiVy5rrOh5xYh3p+P3EfQGleiSFxVh/KeCVXCvKfd0fKBVDJIhYkTN+Qy0ql0ggXkEIQemnHktiVltRvl/w0J2opdJC2NkfFBmJNB9LPNLyVnWVRKs/FZxD0C/JKJXBnlhKzm+YNueivY7SP9tGYquV/tR4B4pWX9j03bfTv53z3dK1m/b29QPSlWG9sHbaq6MmZZ596FKO7cbP+/JKqkw2i5VII94h8xs/KvYK0rVr0sJU4JbXqvwk4eKIpp1f2fCvE+USD5jPzBzcvGn3Me989s3UGduv1RAJSJjh48gQG2V8TwP+ZvpDJTguLNl4vsiOSq7dnRESEBweFR0TExHbbcYFqxQPPo7MQnqlBvwyYjG7UOGC8cqiGcs37dyRPXPc5G2/l7kez1RbKFNv8M9oojBEwVRrcbicttryGgdBRZIzjx+6X6jiRv5IqVLqQHLq8cNwjLqd+CSjamHA07eazFZcncfrAgI0nALUWg1X56kMQUEGDacQThcQoONlZJR7F/EdVatbqcDHqGI7v/b5rEULprz1fLKaUVD71NTU1BgRLmbA5PUHfj2xZ8WEDgEe4KJ7fbJi+76dOdNHNlSL1O+QmtqhXaAkLqFZU9EmKeeowq6Nm8oMkRL+XGpqaodEBlyTDqmpHdoZ2KgaZUzddOTcuSM7f3y7U7CnVPUypuUcOHnylw3/G9ZALWFAvtOrfCa5t7gFD74lZODqf2oEpInp1qaRYUy6lBiNRuPbVP2pf1hQnFTuH6pnxDX7Js+E4o47KzupAOAHU02NsThVknbdg2LxEhvlvF8svaR4lJQhFUaj0TSdgXqr2WisLWjJQtd7db4d3ZLqY+/HeULbfdUtO7q131nbXesm+eP9/1bY6zhi+f2zBjz4FE3fg2aUbjvaT8Wgay0i4gQA9Yg/CUo2rYxnEjjhFkHJDyeHAqxARDR2kqTbj55+XcpIJyLiHBZ7ERErUhg0WVWDMl0XhmuYNVtdhTKrljUQ4wMb95+0p8YDxGOCxeV1zutL+gZz4FNCZlSj/PKPdOzUE6tRLtmTwCB6tQ3lOleHidVI0yrLwWwPq55/IsOaL/RsuAF/IcMLXUQAgNe1WXzNSlh5vPbU4p23a4l3VX8UxYOiFRC51oEszR/xzN4wIsM1BlnhGwjKd81SsdD97MM630JxW1lRwQMzEUHr5zyTkffQrcNkcrrBa13cAPDxw+ZvP/6vU3m23xekhTUdPuNPl1fVjNODb9F+LyBN7h9dNv3rWTmXHolgWR9G77XOR0TXvcMr5n2ffaqUiJnHylHPcaG48WLWrOkLd/xrR8TKnksZaJbfuCp+zUQ5bl+VO9R7oo8jLfw5e3CrBvWa9/r8VyuFZf1YPF+EomX7vxzy/PPDp5+oFcHzDd0BqMIadZhdoyiXw1554cf+0RwAhL10welNphnBPmZoLdLF37YyAADw4f33Oik8GMzmo7WIwpX3WxgAgAtqt7BMBI+HyhhUhaJVa7qFcwCgqf/KGSfikV8ZcGGx4jH1T1MPOkXHytR7z/su6tGcRHAb+uYdCo+FykvMRdq6pYsORIPSTwoUrtBIAADduxUKct3fuXTe251jVSAaMHBXueA95v/5mKCDSF9J48B96HdOyjSATV4VWhYlgHt+aKHIo77SQg6h6NVBGnAfNduM4jKkGo5RJY3BgwoL+RUR0TlVA1Kfz6csg2Vx3yJd+1UASIzbIFDVfaRp3yhTjL30yLjG0eF6Dtxrnvn0WFGN3SsIPvoswLekVVN300By6B4KF7NBtE3Wg+RXzRTOkZZhFvm7A0jWfmGrW9pXULlRIH2CExHxB1nN8ynXdDVIjtpP4WatJPXIhx6zVVRbah/evrzx3WcDQC4X+Mywqdmn7rmUh2iZH+pbPkF6BsgcaKbOBDNaGwDSDTtFDhuk8FlIVw8FmQE5dctIJzUHZCbk2cxm+88GOV8gfSgCZHYsoe6nSNK9Xc6M2AVE24Mr67+c/v3siSM6xWk4YMip9JGt3j9U4lCec0+ib+nxxWefffZRAzkxf1IF9dn82wLkvuSkbiZISbwuskotB1KK6pS3CfWBHL5tr549e6WqZQSdoKzDQS63lML3JYUttcghty4XlFVX3vtr7/L127cu/aBPokYXqFfzHHhQ3/qdnMvF1Q52FjNhgJfbcD6FsWYfVZ7CZi7IblpMlbaS0tNEVXYF2fzyOuUNkW/kMG5dRp2PkAU9jdQWtZQGB11ynD/0GT3uo4mv9W8eGVcvOUIFylQldn/p4++2XbxndgmEFhzVVXaXy+U0l986tPw3gcW1NN738Ruo6g5MHj0vL+QcVdtFyntIHw+UBwOtdclgO3UyXAljXdQikB/9B5UXI4H7z18o99EkHai1Gg4Uz2nDG3YZ+/nSncfP5uae2L7gvXdmrly9ZPZnr/dv9fwZrKO4LJGOTK7GydPspMw9pMwWWQgMG+TXJS1KKMc3BgVMRkQUXmOg+om630aCKj1fVv4wDrxYFRDdsFnLZ1u2aBCu1YTGxEaHBagBBl1lcjEF6rSjenncBsraU8oqkTdYBP5al+j3UGjJaqfx2DLK3JMBLKCM/aSMKpR1KQ1878ibLBzb4nwap9IYQiLitnpgm1oerJbFr6ccQ1hodtclMMxEIT7Y+mabMN4T/Aaquj2Lryn7ixLUo4tk7W7ugwb8w8LyXahP0ianDR83de6SrJ/2H8u9cr3WA+s4BivlbaZMPVlwG+oU/TIigugqu7B2fFq8htlPlGnRl1/J/mI/5fyvBNWwAjmOhWE+qN0ZFqapQb5H/ezne2/XOgnKZZSjnEfdWUB2nQKxm11iNKm99v8POwUxUYl40DVeAt/zmhzr3BAfFL3OwaD2Q4Ov4dqvKSHI1P+D8NnlEmhSfXR8ohdMkADNjgsyyNEukfWbNIoL1mr0QWHhwXo17wOisuwMat7S+Rjt20XI+jEA1N22lEpCRCHvDYM3RW2wycCa/RsOnDi8Y/EHb325NGfdiikv9GgSEazjFcRpgyNikpJjwwxBEVFREcFaDnpfQYb3hqh8i+rjRyhKzPdu5OUe2///9Su/m/Hb4wKAtv3kQyUOKYjWZdFsnPk3WF99SUrwApMcdDgEIrgsFaVVZpvNUnP/5uFVM97snZIQxHuI12jUKnVQYuoL01du2ffzTws/mLIsK2v5N690SviklMXfncG3DK1C2nry817N4sOCA7QqAOAyvUvliZy6BwAMzYfP3H/b7A7JmiAmpd1i4hnHBUgJmv1IFkOX1VR559z6j3skadmFNOs0cOzY4RkjPtt45eEjq8PpdJhrHllsNqupLG/9QRMD4UBj3xJ9Fuk7b4SAZC7Lu/hNlLUfC/4nX7XPM7QuuddnO4oEEXSMl8FvFmkJSozKtnlOVHAY/8kZHc2xiej4xb4/C0rL7hWXPHISlC04HAID8zdhvmWsg7rXF2R6G5dNCaNZ6A/6hHkMDMc8R2uafXxVBPPipXGZVE2qIpJ+ERSCiMR5Y/7ABnp5ho4zz9YQJKjsi905n8KtRXoGyMr2LlhC4ScsIvO8bITIEgbhl5UBAE33itiGSIOFlKWvElSdLhIPESmIQu3V7ZPaBHBSOE3sK0eNqHyyMg58Ssg5qrKDLNVWL5ssslnFoH2Flw21U+s4eS1KFANNr1I4Q8bHFI5XgKb7ViN6nEhBRNfD45+n6N2owruMy7zlQmUTkcxE35Jwi7qRICv4jJeNdVHXkxmMJ17Wz0odC5D3glM5MF9kjYx0G7WO9xRn6HPYisoXyg6+10BDBXb7/rcHVvTObQ19S3Ih9Xe0rNRyL2tbTrnelRd6Ar2so5G6U1+Wbguy4EQZvC2yiZfWvIS63dgz6ph27x63o1c6i9cPjuT5euNzTQJ664Z6viX+JnW3iRxuLnpZyBkKLyTKesXibY0KKftoWT0rmfTMzMzMzH5Z3niRNSDd8AtFvmAwYvnSpcsWNAWA6PRF50qd6KXE/vu8gT2WFrvQe/PeCPUpIbmUfZScbve9DaaJ4I86GSnX0NuCz1C4I0BGwnFkMsyBiHg5Vg63UmSKDJhEEBHvtJeVeAUR8WYj4JrPumoj6MXEciPvEUEvFq5ObBPO+Q4um8KDEdJSLqHXtS4RscwIkpRyGr0OvhexvCUtZh1h07iAck3mZbQpoCwD5DS5TeHhejIMPxBqtYprlVmFdS0Rd4NC0dGFAyM5XwGvOinnojAJmkG/o/fx34ugfVN7lZvgl/9CtFq9baCZwvuvat2pOhwQsOAUC3UOhVXjdJIa/oL0xSg5MI1QeLwdJyXmeysiYmUPaLzWiHU/Mf05+xmVR1TBIRID5oi8YQiRqJeUcIVC5/4BETwA8JG9sioRbx/wNmj8hwjivVWjUpITGqV9etSCSObv87awkyJoXN8vwaDm9fH9ljxAxMnTWEC3MgpNWZ0MYlzEmItIO8eD7ISzIlgwvYVeRJ38+lkXIiJZpIaB99AvJFVb0rSeaHvi/Dn3ucUU3so95/7iJEnwrp1CNF3OmT11/oYrtYjomjTO6yC9VAzRUfbvzcJagoh4uf5Gb4PRJhFE8/WjO7YdvGZGRDwbN4WJ6lsXhVhxeObrGQOGj1tyyYKiO8PkQY9CERRKDsya8PbEmf+/6UDRA7EAQ8r9A0Tz/jTeAz3M6NkfpAXniEnfHjze+/g3yt1ILe4Hm71Ov5SISc7vAdOYQNgqhwgiCnabg6Db082A5bC7IvJPtACAjFJ/Ac2ZQR7orihI2OGSQ/bVBx8A/LBrsm4MBh8AMZsFWbcGACsInlnjRrJzZ1Ng2/syC+v6ZFAIsTjqBjwb4YEeyoKIWeXSyufHAkzwwDoWq0R6yQFovvyhpMqcZwE4RTTxEETMLpdm3d0OAKYzAu3AI1Y5zn8mhQHr+vNLiAzHhZeCQCmuazuOXy+pMDsEX3cm3AOdrhUWeLJ4qhzQdF6ZbxchlhvL/6MGgNeK794t/qetjNR/CgsKiheymF1cUFB4o4s80LSffqLY5HQ5Lfdz56fpAIDbItKRkX7T3YKCogv1PQXqrpn5djHX/d2jggEAJt4tKCjMay4PICxj7Y1awY29ZM/rSRywV6VMP1PhEiOWgm0vxoJ4ugJI8exBA0a8/eXsn0pcyiNKIqc9oUtM8mhymCwAdcMhXyxes2L+B/0SVUCHNGrUqFEDnQxdYlJSUnIEMAxPTkpKStQxAIDApt2Hjh3as3koB7RmN1X1HCMuOjkpKSle7TEAdcOhXy1Zt37ZtDEt9SAakpyUlJSgYQGgSkx789tFmRvWLvwoo5kePB3S7oWvl2SvXzF3Ut+GWnCf/tBzKOSvGxmtCwxv+t6xaqdSiNN4/+rf+YXFj3yEHxt4knrQkpHSOQ4UzXEcKJfjQG6GEhDtt5d0VAMY2n5+IN9EFOH4N+vd3ml93thRoaRTflm9f6mb8T6hTs0oVQQS895uCckJUeHNP/5TUIRQfnbNvMkTlhYKjwmqpEaNGjVK4BkMsVGHDY8niNYjmeuzFr7//vaHRBFIXHa71WQlqOTT/kzw7uLCwru5DeXptyI9C/ztdDl3z12pJGxQcAmCs6baid5IFHLKn+GzERHJ/3hZY2qpmu7+ne3SuE69ZhYxqgP9GhhjR0Ssekcjjet7B+k9AX5XhgTBnJ89IAigQZbL2wiSx4/IXymsXfqsyh0X/+k9pCv6gt+d/tCN6+95GfFqAAj8yuZtqIzqIrsc4t/A4AoKSfH6Nzs3jIutlzJs7p9OpJ3fqvyvjDIx5+UXIzVA88MKlUUICyVa1rx+3OFXqT58RCGiYLx781pBqR3FXatCwW8zHR4WCG4b5tgU5aXkUo+gAbvKiB8F2nEPxOTWzg0DPzz9ISLa/1ncUQfuVf3zfB65/pYB1C0/O/JA8J+AT9v+SJ7j7HAt+OMZpUjurRkcx4HU0EkFPosQSvh7XDgAQFD79zb+VknE0O8BCBiUdcsuxXV/72tR4J+nl2L1nAYcyIye+IfNVyEiqT05NgTEdfXTvvzL4TcBqOplfJF98HTen5dPbJvzUks9+OsZpZaNzTmQHZbx0wOnQuzXrpuIgpBYK/Lmd9GD1NDh+yqJ30Rz+qCwiLBAFfjzw+7v68gDQ23jCQceOJVgO5jeZ35eDVGM6++VE7uGq0C6LmX6ZSNBctpfeizsuaabGphyhhaTfnN5rmZ7dzUf0m32baIQIe/lSB0PsrmwHnNyy4XzEU/OIhvwwDzw1d9cHiJ3F7VVAQAX8d/zNnmEMLBdfDkE2HKhXT/ZuSnsyRlw4MGQUT9XCh4gpnMTkzgQDey/vtglRzYRLDcyBwYBe13jDponaJ7VtPpwb4ndKchyOex2U+HB6WkB4J5Pfmntn0a708XIZS3L2zJlQCIPT0W5oBZj5/105lalnbhzWQr3L14w95OMluE8SNYldpswZ9WuW1biTnAIlGArPTjjlf80COXh6ak2IrlVr/e3/l1aa7VZjQ/+PrBgaJOoyIggFcjnDOHR9dKX55VZnILTWlP865aT+ffv3Tm16s0WYVoOnrryhsTu78xduXbVrLe6NwhVg0fV0V0nLd2yfdOKqS88E91y8IsvDmgZoYOntbw2IDAoyKDlwfO8LjA4JDhQr+aAU2s0Kg6ePgJWUDggOA8AAPBHAJ0BKvQBWQA+bTKVSCQioiElseuIgA2Jam69wScVhwyEAdwP7T/APwzl8zKs/kCzdVG7ZjoHaf6V+1v9v/Yb5cqz/ZfwX/bP2g+WXjNHX7Gf6v3SfL3/Hf6n+4e6D9E+wP+m3+o/uf5F/FR+o3vE/sn+69RH6y/8v/K+8j/nP3C92f9x9QD+7f5zrKfQA/bf0zv2o+EH+qf9H9tvaF/+XsAf//1AOFr/rncDkVyFZNg06ECFdiZamZaGJko+Wj/wcATek9b9oeXHDXWzKNuLSTpZ3rq6Ch65AyjL03pvWIYBn1u/jzqdm8yhI52vdVCYEoUqhVt32y+HGP85u8cUZ9EmFZy1JmsesYpNxHQMToQ+jtPe/tXdxNnTdd+H3zFaZOcmSiHU3CGs7MR/USDnPuvu/TVbv/elzhNggppm2VcF7jzZF7TKUJnH9cZoXFJj15NYXxGZUmna+OrJDh3Ffg2v/5FVK7g4ozINruMnH7SN9NuSvhxJq0/9YgqwqspCZ64HJQLLk7lWahyerjIlwnV5YVDq4fKacCVhdChrunZe/y3HsHeEBeNF+WiUwXsJmYiehsNdGrzjvR9FNoUkf5MIg4VWd4dVF2Kv0tc9J7D2G/+OWErtjxL40u+8kFyYAg73sg9JJZJ/EWKwxa9THQMnemN240ApFJ1pZa71Is6rjPiOQKk8TS9ayNmfQF7nifyRJutcVJeqH7BuMMlxXaLja3sksqGnczkqap7swB9w6cWbSp9UKUWzpyPqhSqFW3fx507GAP4FDPhUQ+cn/obKNXd4U7sOjafzChPyBf5QXcEy3lLbBH5vvfWJg1XAIUSJDAU6Or57Bv5Q48YArWWS1RHTI7GLUMPqWLp5uCVns9F8jvv/z3j9/qr4pA6r8KASJmFQb4jfmtHEH2mYTRrKpdJuMDFxV5bJBp1MnPQf5ir3T5MGgWO9MaMFDCBR4M8xsfVbwbVUBXrqkuVGcUv225N6MZVOIYlSuhoQM6vFE1DjYKCZP7Jhxwm5rpLuraMD7HqHXdMnwuaGGslyaqEofOjbWTCCBSFvEpLyM4ASx/KRdxvQNDL1GrXULTx4TGn///xcAfVW+ljEwlOk5BnWmH/vw7Z5gOkC1dUoXa4TjhvU/6xzvdlWshJZw/I34/eCjlQ6C5V9dnHrQKPwAfMfAKUW1zjYFtfsDqIxS/JHTfU4d6ucExX4f5HWzy5o1/i/Ez+dxPBxT39GVHFWVyy/zbWe4XSD0JdMVHQiz6fCvkDCA9f6MDFmsZtcD9pIrhgRUjo5elG29upiHJWW5C4ET5oM8L5xUsxqon0Q7yCxC30GZ3QC2tmgL6lux2jc2dTAbTQ1/nFTXrzZBaQfyrQDzefjBxb72kieqD1zfGCvGCv70gY82enYtb2wYVOqZvL2XxzeVyJ7Vo3+AOLxcLtp78W6/69XoJyXTVWyGcm4TnLBdI1yJZ4hR/vifwEkZqocB/oLysBw9hcekcU4okKxASg5M+j/A4GOZEEQY5i8TKvLah8H2BMbj6CKObTAzQ3hjnI7n/g5i8ADZzQWB3n8EQ8sb36npLNE4gncBeeQqu+If8cZ++rGZ++6lt1lTD2OgaEvk3L12455xU9Fg6JF1R6s8rNo6wsGN//yS/xYYufzdTWM4ELrMpZwki3lyM1uZqZ6rYor0ds2G7seG3iYDdps5x6NqQx67Hg21qc5JC0GxOhfph+FT9w9DZAA0+xrEm3VM68nNwKSIXRHdMi4ifr7KJ0/6pgqoeeuWbf5eN9vzzTQUvoxxJ4C2glwKVXBjEDtTIDqojsY9JoF44prXqM8PAle5rzt3gGNfLQswmSWTr0kLOQiRo8oRH00eNNsPuGshkKhGnWA9cLdMQ3ruZTWXqqLPiu+7197SAPLqvU1ZoQs2whTxCoeSa7x4Ooz1MeT5A3jn4pPQjCcZHJemtU37APjuBquvpsilyUjirlVB9QjrZIwt0WeI3O0QXJ0VlgALOtTBcm4RbwvBdGVcHBWERaoiz/EDvaf8LjcPD9PJwR5G+kVzbW43UopHElebJvHOGpYghqdn3qMXt52I9FuzStVDADGO6T/XR5k3bw0Nadfm+eNf9OL8usccQuYVz9jPglfZvt7lJz7sf2rhNfynwVQ+BCjIxVRS/yrsjxc1iElWoWA3Y+LwwXlWxvpAofEycRgQXf2NW9YqTxSt1B1gbReMnGMQDp/R6xI5Emo1dSsnVNqw+XmQfFEzKUmuD5VOxqnPHAFVroE8lfVv0TREuQIxrmKO1HDa8iMMsXt0WBoIxF91H/VLoKQsVwrCi0/5BtGOFWqdNAlAfgTrZU0kVBMcE4Oo/XBtUdyK/hs5Gqu4jjIyhHHmrKPdnFjMhMXbvdb/H+IgwEOrTTD2pL3qS46UhrWGM74YLjSZ1mlw233n1+UZlHsQMvbMKp1fwfwDUvjaPKMTV9+ynHUSF4rrm+F/ushsZEJxlX04ehM/RahXiDUs793CEtIkbtWAFObPdme0WLyUGV/FAVLlnhxEIL9E/gPbE+cd1W9Yh6uZZcNB51jRChvQXCjNecIh2Q4fy6gpdAzrejrIYtarLA/sQ4WC9l4480Bp0A1PBFt+rkCfA5op0AhvCMqom7N//hoVkNdWb7jQTGuCV7+oriNdS+qsrhhau1hsWRjL20viML2/xqQbH3glI/KTaLsQU9oyNEwTXe9CsWxh4rnWQBkqKBE4rShFzIEHKjohwtNo4Oq9888RkzsIBzlH+nEo+qRV1CzREOAvLUBQNbK1YynIHvcEzwUBgJCnAkBMHSMqj+zQnOSDP+LeMuIK3H9XL//XxK0Na81RVFN2DtrtvE2B0tl11KS7fHrAJa53vsyjKlq3NBnh6SLeGo7lBCF0I1z/WxODmW7Ap0gwVMKZJpXHLeB+r/dhZaEHGu4WqX/yAGhYbQnoHmHAUhN4LPly40m5Tgh/JnxravYQ3cbrkFn4Q10OghwAN3G1Rl5Q2sdm9pC06/vYPO/Qk+BDfOltE377ympui6efdiY/iHAXNUqjJX8gmLOQH/Q8Ztqi3feYcPPnAjMusCNRKa1gicx666LGVPwnBJWsL289qryFtV1289fQFMY1vk0NxEzQV+iRtDrRRiQ9wl8yvP4thAw8Kn2Gix1NR7eWhW6x58WR9afvb0CQQldBU8xTh+oq5sjWBCJN8yUbGaSQXcvhwPf78svYA+iKmIAJSOqJSUtSQdPjrYAcsW3Fk6BQL46Y067rXiJmRI6hcrU3zMtWxQQJT8gFMvjXnrOBxWR4ou514qeSSykPzI0EbjIZlXWgWiW/mA9c9jygh2LjW4m2ckqIynsoWRezcp2a6pxKTxVp/Vs+P7J1ExZmjejyFZd8adwpIC8pflLq4nC/24uUhWxB462kbGK4aRRr5PVzWmFTK5d0nY5A3s8qlMUBK+bnWxC7t5311w/nNQ+oCVVA5gcHwDjXsEd94s5k6PugjDSM0JtiNZSe4MQ0XIKP7TvaoRUht8GvgniA4E0W/M1bMVgHU8KGKj+siiNNcbdLxssJo/cWA1ns8ZActD62mAXfxfwvV4bI8yPeGqzE2gH05hzrrTR7o0Uy8I3keMbzC9Hup3F2Zbyf1CAibgYK8+hB8qldW8zMB7Pr5StRNTqxrbCxoCDFgB5zDQUF0msP8l9itNZ/nxOYvXLT/cQmlUuYKMKJgg/NxMVewn0+JS+mK/NdQn7Vwj8zrSqU9dkcvnQQJcbMaIO2AKCUm1qvpAkYE8+mxGCCyWtunWwBhKNT9QSt1YfIIflwFa4fHdCTo+124aNyeozbwKgKwNlD/OkuRM9OsC5PfwA47ZX+2pTOFyE1qSp6v9Mr4B6Mf1YzjZPlq+d/19kOGrGARvdCV+g5v9intIG8NYKZZc+wTcZpkq48hVFpsG2cb2YbjQoZfGFFA5d+Rcirerj/75w9yrA+j38bll84OhG0nc3E85WMEFddwMAoeloiMP+nSb7xAGt3DcoAYhZADIMCgzcQkDsO1OBE0wB/nhrvbzxAIAtTvPu3PdaiXlddPYsVODTpndZQ36yPrRxhEQMrcabVnsteXspQ/fYCAkk9yqRqYx0NYao2V8qSY7w1oTFt8GGFH6J9G7BJPNr+RSxyWqATv8KtkHsYWBy768GYd5cQulbIvlFtzAN2PFzwO0cMNV7XwIsETHHXQL8iTfqrRedQJVYOragHOVlLLcE1iCYkKQ2OIufjCrM3ydCO2wQIruGOC/5Wc/dYohGHxsHEvCW4dDDB22nLo12BAl6uqR6dd2RxqM2TPbAkv1DAb2kTGxsd6BbpY6q+FDIfCm5VjJLZ3LVrfsOOrXfQ0IM8Qj7a3tB8nuMkXbjscYESBkZp2eN1pNIkGvSl0Esw1uu5OErJivjmEBtFVuRx4LJC9vz/SwwWih/gptF4ic7UWPTzSRZEocJSAMqfAPKdCKQxYgN3qv23oZKKScWDcbElhvFVIGbmhjnCOjaeJXVv+C6VAGgPLHR3asMb6VcZVXDuBWpNvVPJI3IdXImATt9SMG7tWDlfjuT8X0HY7mabFNwi4nl4FpNyKFmm6mJPdn00K05WYfToKGmzzg2FoYTweCm2b06iKORarOgeUsBJOECel6RT2pNMAvP5DrdugBmUxXGi2rAIK6RYWbmcgnt9jq0vwitdSnPP2qUNecK6rzXBpoR3JWKFeSieOaTpiEhxkTJ29/IhFfLk/akWaiv1EcZH/ELxhS+uwgGaErwBnBl1k3hiApJDTFjCUYtEUzGLSXJuGaYvKevxeK2KpS9llgBvev9Px6nLY/YHlHVWbYtfQ/UeTDOaK9mHLy+vEFVAndmii6icJdHJ3utNpQIKgrG5cmJ6jLjYeOTRFkis5QKxqzJrWr4rWkwqY/g1TJqbtw/h/tmEmqx8eDJ8Xd78M4DUdCbJO8ChsAexv7ZmA9PPcvhnAqMs3/bflf4T1zLrRg4nQ18gjv5W/OISSUrK1ItQVF5RWznfu8xS4NTcNwb3cBUrFA596F2LzND2Yhxw9xqeftraLnIdXxRx61oNOzvTiCfWrJifuGstlIYqNL5wAxGBe0IHNmniWreRvk+fM6y4J/srE08MFzGdu/5+RDXVLRC38Nudabw7M27EzVNL/T5qYx7KRxrEfqOOBeyiuWAO7lR2wNvn9xEaWwtcDrQgAB4rM3WgAAA\" alt=\"Langfuse\"></div>\n<div class=\"skillTracker\" tabindex=\"0\" aria-label=\"Skill path: 0 of 8 skills earned\"><div class=\"skillTracker__ring\" style=\"--skillTracker-pct:0%\"><span class=\"skillTracker__ringLabel\">0<!-- -->/<!-- -->8</span></div><div class=\"skillTracker__panel\"><div class=\"skillTracker__header\"><span class=\"skillTracker__title\">🎯 Skill path</span><span class=\"skillTracker__count\">0<!-- -->/<!-- -->8<!-- --> earned</span></div><div class=\"skillTracker__series\">AI evals &amp; observability</div><ul class=\"skillTracker__steps\"><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">1</span><span class=\"skillTracker__skill\" data-state=\"locked\">Prove a model works</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">2</span><span class=\"skillTracker__skill\" data-state=\"locked\">Trustworthy JSON</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/synthetic-eval-datasets-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">3</span><span class=\"skillTracker__skill\" data-state=\"locked\">Real test data at scale</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/\"><span class=\"skillTracker__dot\" data-state=\"locked\">4</span><span class=\"skillTracker__skill\" data-state=\"locked\">Observe &amp; score production</span></a></li><li><span class=\"skillTracker__step\"><span class=\"skillTracker__dot\" data-state=\"current\">5</span><span class=\"skillTracker__skill\" data-state=\"current\">RAG, end to end</span></span></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/deepeval-regression-testing-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">6</span><span class=\"skillTracker__skill\" data-state=\"locked\">Catch regressions in CI</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/component-tracing-agent-tool-calls-wiline/\"><span class=\"skillTracker__dot\" data-state=\"locked\">7</span><span class=\"skillTracker__skill\" data-state=\"locked\">Trace &amp; debug agent tool calls</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/web-search-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">🏆</span><span class=\"skillTracker__skill\" data-state=\"locked\">Add live web search</span></a></li></ul><div class=\"skillTracker__footer\">Finish this tutorial, then mark it complete below to earn the skill.</div></div></div>\n<p>Everything in this series was building to this. You can prove a model works\n(<a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/\">part 1</a>), make its output machine-reliable\n(<a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/\">part 2</a>), generate real test data\n(<a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/synthetic-eval-datasets-wiline-inference/\">part 3</a>), and observe production\n(<a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/\">part 4</a>). Now we spend all four skills at once on\nthe pattern behind almost every serious LLM product: <strong>RAG</strong> — retrieval-augmented generation.</p>\n<p>We'll build a <strong>docs assistant</strong>: a containerized HTTP service that answers customer questions\nfrom WEC's own documentation. Not a notebook — a service, Docker-first, the shape you'd actually\ndeploy. And because this series doesn't do happy-path demos: along the way our RAG <strong>hallucinates a\nGPU price</strong>, we root-cause it to <strong>our own scraper</strong>, fix it, and pin the fix with a regression\ntest. Every command, number, and error below is from a real run.</p>\n<!-- -->\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"what-were-building\">What we're building<a href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/#what-were-building\" class=\"hash-link\" aria-label=\"Enlace directo al What we're building\" title=\"Enlace directo al What we're building\" translate=\"no\">​</a></h2>\n<!-- -->\n<p>One architecture decision up front: we split the work. <strong>Embeddings run locally</strong> (a small ONNX\nmodel — fast, free, no GPU needed) and <strong>generation runs on WEC</strong>. This is a completely standard\nproduction split: retrieval is cheap and latency-sensitive, so keeping it next to your vector\nstore saves a network round-trip per query — while generation is where the big model earns its\nkeep.</p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Prefer managed embeddings? WEC has them now</div><div class=\"admonitionContent_BuS1\"><p>The WEC catalog now ships <strong><code>bge-m3</code></strong> (1024-dim, multilingual, batch input) on\n<code>/v1/embeddings</code>:</p><div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-s</span><span class=\"token plain\"> https://inference.wiline.com/v1/embeddings </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Authorization: Bearer </span><span class=\"token string variable\" style=\"color:#36acaa\">$WEC_API_KEY</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Content-Type: application/json\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'{\"model\":\"bge-m3\",\"input\":[\"first chunk\",\"second chunk\"]}'</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> jq </span><span class=\"token string\" style=\"color:#e3116c\">'.data | length'</span><br></div></code></pre></div></div><p>Swapping this tutorial over is a two-function change — replace the <code>fastembed</code> calls in\n<code>ingest.py</code> and <code>main.py</code> with the same OpenAI client pointed at <code>/v1/embeddings</code>\n(<code>wec.embeddings.create(model=\"bge-m3\", input=chunks)</code>), and re-ingest (the vector dimension\nchanges from 384 to 1024, so drop and rebuild the collection). We keep the local path below\nbecause it's free, offline, and every number in this guide was measured with it — but both are\nproduction-legitimate; pick per your latency and ops preferences.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"prerequisites\">Prerequisites<a href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/#prerequisites\" class=\"hash-link\" aria-label=\"Enlace directo al Prerequisites\" title=\"Enlace directo al Prerequisites\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\"><strong>Docker + Compose</strong> on a WEC Instance (the same setup as\n<a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/\">part 4</a>'s Langfuse box works).</li>\n<li class=\"\">A <strong>WEC Inference API key</strong> (<a class=\"\" href=\"https://wec.wiline.com/docs/es/cloud_portal/platform/inference/api_keys/\">Inference → API Keys</a>).</li>\n<li class=\"\">Your part-4 <strong>Langfuse</strong> instance and keys (optional but recommended — used in Step 3's tracing).</li>\n<li class=\"\"><strong>~1 GB free disk</strong> for the image. Check first: <code>df -h /</code>. Our box was at 99% and survived, but\ntight disk is the #1 silent killer of Docker builds.</li>\n</ul>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"step-1--scaffold-the-service-docker-first\">Step 1 — Scaffold the service, Docker-first<a href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/#step-1--scaffold-the-service-docker-first\" class=\"hash-link\" aria-label=\"Enlace directo al Step 1 — Scaffold the service, Docker-first\" title=\"Enlace directo al Step 1 — Scaffold the service, Docker-first\" translate=\"no\">​</a></h2>\n<p>Real RAG features ship as services, so we start as one: a FastAPI app in a container, code\nbind-mounted so <code>uvicorn --reload</code> picks up every edit — no rebuild per change.</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">mkdir</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-p</span><span class=\"token plain\"> ~/rag-service/app </span><span class=\"token operator\" style=\"color:#393A34\">&amp;&amp;</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">cd</span><span class=\"token plain\"> ~/rag-service</span><br></div></code></pre></div></div>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">cat</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> requirements.txt </span><span class=\"token operator\" style=\"color:#393A34\">&lt;&lt;</span><span class=\"token string\" style=\"color:#e3116c\">'EOF'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">fastapi==0.115.6</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">uvicorn==0.34.0</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">chromadb==0.5.23</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">fastembed==0.4.2</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">openai==1.59.7</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">langfuse</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">requests==2.32.3</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">beautifulsoup4==4.12.3</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">EOF</span><br></div></code></pre></div></div>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">cat</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> Dockerfile </span><span class=\"token operator\" style=\"color:#393A34\">&lt;&lt;</span><span class=\"token string\" style=\"color:#e3116c\">'EOF'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">FROM python:3.12-slim</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">WORKDIR /srv</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">COPY requirements.txt .</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">RUN pip install --no-cache-dir -r requirements.txt</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">COPY app/ app/</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">EXPOSE 8000</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">CMD [\"uvicorn\", \"app.main:app\", \"--host\", \"0.0.0.0\", \"--port\", \"8000\", \"--reload\"]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">EOF</span><br></div></code></pre></div></div>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">cat</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> docker-compose.yml </span><span class=\"token operator\" style=\"color:#393A34\">&lt;&lt;</span><span class=\"token string\" style=\"color:#e3116c\">'EOF'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">services:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  rag-api:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    build: .</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    ports:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      - \"8000:8000\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    volumes:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      - ./app:/srv/app          # live code - edit on host, uvicorn reloads</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      - chroma-data:/data       # persisted vector index</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      - model-cache:/root/.cache # embedding model cache (survives rebuilds)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    environment:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      ANONYMIZED_TELEMETRY: \"False\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      WEC_API_KEY: ${WEC_API_KEY}</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      LANGFUSE_PUBLIC_KEY: ${LANGFUSE_PUBLIC_KEY}</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      LANGFUSE_SECRET_KEY: ${LANGFUSE_SECRET_KEY}</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      LANGFUSE_HOST: ${LANGFUSE_HOST}</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">volumes:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  chroma-data:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  model-cache:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">EOF</span><br></div></code></pre></div></div>\n<p>Secrets live in an <code>.env</code> file next to the compose (compose loads it automatically — keys never\nenter the YAML). Keep it out of version control from the start:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">cat</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> .env </span><span class=\"token operator\" style=\"color:#393A34\">&lt;&lt;</span><span class=\"token string\" style=\"color:#e3116c\">'EOF'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">WEC_API_KEY=sk-your-wec-key</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">LANGFUSE_PUBLIC_KEY=pk-lf-…</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">LANGFUSE_SECRET_KEY=sk-lf-…</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">LANGFUSE_HOST=http://&lt;your-langfuse-host&gt;:3000</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">EOF</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token builtin class-name\">echo</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\".env\"</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;&gt;</span><span class=\"token plain\"> .gitignore</span><br></div></code></pre></div></div>\n<p>A minimal app so the container has something to serve:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">cat</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> app/main.py </span><span class=\"token operator\" style=\"color:#393A34\">&lt;&lt;</span><span class=\"token string\" style=\"color:#e3116c\">'EOF'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">from fastapi import FastAPI</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">app = FastAPI(title=\"WEC docs RAG\")</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">@app.get(\"/health\")</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">def health():</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    return {\"status\": \"ok\"}</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">EOF</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose up </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--build</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-s</span><span class=\"token plain\"> localhost:8000/health</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Output</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">{\"status\":\"ok\"}</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"The containerized service alive — compose ps + health check\" src=\"https://wec.wiline.com/docs/es/assets/images/rag-deploy-health-850c74e1ed3eff7a20368db371456f79.png\" width=\"948\" height=\"117\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figure 1.</strong> One command each: the container up, <code>/health</code> answering, and the disk check\n(ours reads 99% — this box lives dangerously). The build takes ~90s and a ~700 MB image.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"step-2--ingest-build-the-corpus-from-a-live-website\">Step 2 — Ingest: build the corpus from a live website<a href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/#step-2--ingest-build-the-corpus-from-a-live-website\" class=\"hash-link\" aria-label=\"Enlace directo al Step 2 — Ingest: build the corpus from a live website\" title=\"Enlace directo al Step 2 — Ingest: build the corpus from a live website\" translate=\"no\">​</a></h2>\n<p>In the real world, the knowledge base you'll want your assistant to answer from usually isn't a\ntidy folder of markdown — it's a <strong>live website</strong>: product docs, a wiki, a help center. So the\ncorpus-acquisition skill worth learning is real scraping, not file loading.</p>\n<p>For this tutorial we scrape <strong>WEC's own documentation site</strong>. It's built with Docusaurus, and\nDocusaurus (like most site generators) publishes a <code>sitemap.xml</code> — which means you can discover\nevery page programmatically instead of guessing URLs. The same approach works on any site that\nships a sitemap:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-s</span><span class=\"token plain\"> https://wec.wiline.com/docs/sitemap.xml </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">grep</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"&lt;loc&gt;\"</span><span class=\"token plain\">   </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># 109 URLs on the whole site</span><br></div></code></pre></div></div>\n<p>We filter to the product docs (<code>/docs/cloud_portal/</code>), pull each page, and keep only the\n<code>&lt;article&gt;</code> element — Docusaurus wraps the actual content in it, so nav/sidebar/footer never reach\nthe index:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">cat</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> app/ingest.py </span><span class=\"token operator\" style=\"color:#393A34\">&lt;&lt;</span><span class=\"token string\" style=\"color:#e3116c\">'EOF'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">\"\"\"Scrape the WEC docs site -&gt; chunk -&gt; embed locally -&gt; Chroma.\"\"\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">import re, requests, chromadb</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">from bs4 import BeautifulSoup</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">from fastembed import TextEmbedding</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">SITEMAP = \"https://wec.wiline.com/docs/sitemap.xml\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">FILTER  = \"/docs/cloud_portal/\"          # product docs only</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">CHUNK, OVERLAP = 700, 100                # chars</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">def discover():</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    xml = requests.get(SITEMAP, timeout=30).text</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    urls = re.findall(r\"&lt;loc&gt;([^&lt;]+)&lt;/loc&gt;\", xml)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    return [u for u in urls if FILTER in u]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">def scrape(url):</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    html = requests.get(url, timeout=30).text</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    art = BeautifulSoup(html, \"html.parser\").find(\"article\")  # Docusaurus main content</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    return art.get_text(\"\\n\", strip=True) if art else \"\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">def chunk(text):</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    out, i = [], 0</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    while i &lt; len(text):</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        out.append(text[i:i + CHUNK])</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        i += CHUNK - OVERLAP</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    return out</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">if __name__ == \"__main__\":</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    urls = discover()</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    print(f\"sitemap -&gt; {len(urls)} product-doc pages\")</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    docs, metas = [], []</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    for u in urls:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        for c in chunk(scrape(u)):</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">            docs.append(c)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">            metas.append({\"url\": u})</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    print(f\"scraped -&gt; {len(docs)} chunks\")</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    embedder = TextEmbedding(\"BAAI/bge-small-en-v1.5\")   # local, ~66MB ONNX, no GPU needed</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    vectors = [v.tolist() for v in embedder.embed(docs)]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    print(f\"embedded -&gt; {len(vectors)} vectors (dim {len(vectors[0])})\")</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    db = chromadb.PersistentClient(path=\"/data\")</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    col = db.get_or_create_collection(\"wec-docs\")</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    col.add(ids=[str(i) for i in range(len(docs))],</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">            documents=docs, metadatas=metas, embeddings=vectors)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    print(f\"indexed -&gt; collection 'wec-docs' now has {col.count()} chunks\")</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">EOF</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose </span><span class=\"token builtin class-name\">exec</span><span class=\"token plain\"> rag-api python </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-m</span><span class=\"token plain\"> app.ingest</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Output</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">sitemap -&gt; 37 product-doc pages</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">scraped -&gt; 281 chunks</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">model_optimized.onnx: 100%|████████████| 66.5M/66.5M [00:03&lt;00:00, 16.9MB/s]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">embedded -&gt; 281 vectors (dim 384)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">indexed -&gt; collection 'wec-docs' now has 281 chunks</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Ingest run — 37 pages, 281 chunks, embedded and indexed\" src=\"https://wec.wiline.com/docs/es/assets/images/rag-ingest-2a1ffde1c98f1968bbf60532fa0a0a00.png\" width=\"941\" height=\"357\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figure 2.</strong> The whole product-doc corpus indexed in under a minute. The embedding model caches in\nthe <code>model-cache</code> volume, so it downloads exactly once.</p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Skill unlocked 🏅</div><div class=\"admonitionContent_BuS1\"><p>You can turn any documentation website into a queryable vector index — sitemap discovery, content\nextraction, chunking, local embeddings.</p></div></div>\n<div class=\"theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z\"></path></svg></span>About <code>get_text(\"\\n\", …)</code></div><div class=\"admonitionContent_BuS1\"><p>Our first version used <code>get_text(\" \")</code> — a single space as the separator. It looked harmless and\n<strong>caused a genuine hallucination</strong> you'll see in Step 4. Keep the newline; we'll come back to this.\n(Want to watch the bug happen on your own box? Change <code>\"\\n\"</code> back to <code>\" \"</code> in <code>scrape()</code>, re-run\nthe ingest, and ask the Step-4 question — then put the newline back and re-ingest.)</p></div></div>\n<div class=\"theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z\"></path></svg></span>Chroma telemetry noise</div><div class=\"admonitionContent_BuS1\"><p>You may see <code>Failed to send telemetry event ClientStartEvent: capture() takes 1 positional argument…</code> — a known chromadb/posthog version clash. It's harmless (your data is fine), and the\n<code>ANONYMIZED_TELEMETRY: \"False\"</code> env in the compose silences it.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"step-3--the-ask-endpoint-retrieve-generate-trace\">Step 3 — The <code>/ask</code> endpoint: retrieve, generate, trace<a href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/#step-3--the-ask-endpoint-retrieve-generate-trace\" class=\"hash-link\" aria-label=\"Enlace directo al step-3--the-ask-endpoint-retrieve-generate-trace\" title=\"Enlace directo al step-3--the-ask-endpoint-retrieve-generate-trace\" translate=\"no\">​</a></h2>\n<p>Now the service itself. Three design choices worth stating:</p>\n<ul>\n<li class=\"\"><strong>Citations come from retrieval metadata, not the model.</strong> The model can hallucinate URLs; the\nvector store cannot. <code>sources</code> is deterministic.</li>\n<li class=\"\"><strong>The WEC call goes through <code>langfuse.openai</code></strong> (part 4's drop-in), and each stage gets an\n<code>@observe</code> span — so every request produces a <strong><code>ask → retrieve → generate</code> tree</strong> in Langfuse.</li>\n<li class=\"\"><strong><code>timeout=60</code></strong> on the client. Part 4's lesson: no default timeout means a stalled backend hangs\nyour service silently.</li>\n</ul>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">cat</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> app/main.py </span><span class=\"token operator\" style=\"color:#393A34\">&lt;&lt;</span><span class=\"token string\" style=\"color:#e3116c\">'EOF'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">import os, chromadb</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">from fastapi import FastAPI</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">from pydantic import BaseModel</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">from fastembed import TextEmbedding</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">from langfuse import observe, get_client</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">from langfuse.openai import openai          # auto-traces the WEC call</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">app = FastAPI(title=\"WEC docs RAG\")</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">db = chromadb.PersistentClient(path=\"/data\")</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">col = db.get_or_create_collection(\"wec-docs\")</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">embedder = TextEmbedding(\"BAAI/bge-small-en-v1.5\")</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">wec = openai.OpenAI(</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    base_url=\"https://inference.wiline.com/v1\",</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    api_key=os.environ[\"WEC_API_KEY\"],</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    timeout=60,</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">class Ask(BaseModel):</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    question: str</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">@observe()                                   # span: retrieve</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">def retrieve(question: str, k: int = 4):</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    qv = list(embedder.embed([question]))[0].tolist()</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    res = col.query(query_embeddings=[qv], n_results=k)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    chunks = res[\"documents\"][0]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    sources = sorted({m[\"url\"] for m in res[\"metadatas\"][0]})</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    return chunks, sources</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">@observe()                                   # span: generate (WEC call nested inside)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">def generate(question: str, chunks: list[str]) -&gt; str:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    context = \"\\n---\\n\".join(chunks)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    resp = wec.chat.completions.create(</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        model=\"Qwen2.5-3B-Instruct\",     # small on purpose: cheap + fast per query,</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">                                         # and its failures teach (see Step 5)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        name=\"rag-generate\",</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        messages=[{\"role\": \"user\", \"content\":</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">            \"Answer the question using ONLY the context below. \"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">            \"If the context doesn't contain the answer, say so. \"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">            \"When quoting a price or number, copy it VERBATIM with its unit and the item \"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">            \"it belongs to; never combine numbers from different lines and never compute \"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">            \"new numbers from examples.\\n\\n\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">            f\"Context:\\n{context}\\n\\nQuestion: {question}\"}],</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    )</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    return resp.choices[0].message.content</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">@app.post(\"/ask\")</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">@observe()                                   # root span: the whole request</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">def ask(body: Ask):</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    chunks, sources = retrieve(body.question)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    answer = generate(body.question, chunks)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    get_client().flush()</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    return {\"answer\": answer, \"sources\": sources}</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">@app.get(\"/health\")</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">def health():</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    return {\"status\": \"ok\"}</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">EOF</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose up </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--build</span><br></div></code></pre></div></div>\n<p>Ask it a real customer question:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-s</span><span class=\"token plain\"> localhost:8000/ask </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Content-Type: application/json\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'{\"question\": \"How do I create an API key for the Inference API?\"}'</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> python3 </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-m</span><span class=\"token plain\"> json.tool</span><br></div></code></pre></div></div>\n<div class=\"language-json codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Output (abridged)</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-json codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token property\" style=\"color:#36acaa\">\"answer\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"To create an API key for the Inference API, follow these steps:\\n1. Log in to the WiLine Edge Cloud.\\n2. Expand the \\\"Inference\\\" section in the left sidebar.\\n3. Click on the \\\"API Keys\\\" tab.\\n4. Click the \\\"+ Create Key\\\" button located above the keys table. ...\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token property\" style=\"color:#36acaa\">\"sources\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"https://wec.wiline.com/docs/cloud_portal/platform/inference/api/wiline-edge-cloud-inference-api/\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"https://wec.wiline.com/docs/cloud_portal/platform/inference/api_keys/\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"https://wec.wiline.com/docs/cloud_portal/platform/inference/examples/\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"https://wec.wiline.com/docs/cloud_portal/platform/inference/models_hub/\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token string\" style=\"color:#e3116c\">\"https://wec.wiline.com/docs/cloud_portal/platform/inference/overview/\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"First real answer — correct steps, correct sources\" src=\"https://wec.wiline.com/docs/es/assets/images/rag-first-ask-301b6dfc7a64a463e0eb1e6d93b53837.png\" width=\"834\" height=\"308\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figure 3.</strong> First try: the exact API-key flow from the docs, with the right page cited. Scrape →\nembed → retrieve → generate, end to end.</p>\n<p>Open Langfuse → Tracing and you'll find the request as a <strong>span tree</strong> — <code>ask → retrieve → generate → rag-generate</code> — with per-step latency and tokens. When an answer is bad, this is how you\ntell <em>bad retrieval</em> from <em>bad generation</em> (part 4's skill, now on a real app).</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"The RAG request as a span tree in Langfuse\" src=\"https://wec.wiline.com/docs/es/assets/images/rag-trace-tree-769e5224814989dedc4896f9e4e1873f.png\" width=\"1904\" height=\"996\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figure 4.</strong> Every <code>/ask</code> becomes a tree: retrieval and generation each have an address.</p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Skill unlocked 🏅</div><div class=\"admonitionContent_BuS1\"><p>You have a traced, containerized RAG service answering from live-scraped documentation.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"step-4--the-hallucination-we-caught-for-real\">Step 4 — The hallucination we caught (for real)<a href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/#step-4--the-hallucination-we-caught-for-real\" class=\"hash-link\" aria-label=\"Enlace directo al Step 4 — The hallucination we caught (for real)\" title=\"Enlace directo al Step 4 — The hallucination we caught (for real)\" translate=\"no\">​</a></h2>\n<p>A good RAG must refuse what the docs don't cover. Probe it:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-s</span><span class=\"token plain\"> localhost:8000/ask </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Content-Type: application/json\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'{\"question\": \"Does WEC offer a free GPU tier for students?\"}'</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> python3 </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-m</span><span class=\"token plain\"> json.tool</span><br></div></code></pre></div></div>\n<p>Our first version (the <code>get_text(\" \")</code> scraper) answered:</p>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Output (first version — spot the bug)</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">\"No, WEC does not offer a free GPU tier specifically for students. The pricing for</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">GPU hourly is listed as $0.66/GPU-hr with a 25% off annual prepay option at</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">$0.10/GB-mo for block storage. There is no mention of a free GPU tier in the</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">provided information.\"</span><br></div></code></pre></div></div>\n<p>The refusal is right. But read the pricing sentence again: <em>\"$0.66/GPU-hr <strong>with</strong> a 25% off annual\nprepay option <strong>at $0.10/GB-mo</strong> for block storage\"</em> — that's three different facts welded into one\nfalse statement, gluing a storage rate onto the GPU prepay option. Check the actual pricing page\nand you find they're simply three adjacent items:</p>\n<ul>\n<li class=\"\">GPU hourly: <strong>$0.66/GPU-hr</strong></li>\n<li class=\"\">Annual prepay: <strong>25% off</strong></li>\n<li class=\"\">Block storage: <strong>$0.10/GB-mo</strong></li>\n</ul>\n<p>The model stitched a storage price onto \"GPU\". Why? Look at the chunk we fed it. Our\nspace-separated scrape had flattened the pricing <em>cards</em> into one undifferentiated line:</p>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">The chunk we actually embedded (first version)</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">... $1.44 /mo Starting at $0.66 /GPU-hr GPU hourly 25 % off Annual prepay $0.10 /GB-mo Block storage ...</span><br></div></code></pre></div></div>\n<p><strong>The hallucination was manufactured by our own ingestion.</strong> The model conflated prices because we\ndestroyed the structure before embedding it. This is the most classic RAG failure there is —\ngarbage in, garbage out — and you only catch it by probing with questions and <em>reading the\nretrieved chunks</em>.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"The subtle hallucination — three price cards welded into one claim\" src=\"https://wec.wiline.com/docs/es/assets/images/rag-hallucination-18e9fb70cb217a347f31057d36b774e8.png\" width=\"947\" height=\"525\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figure 5.</strong> The bug in the wild: correct refusal, garbled pricing — a storage rate glued onto the\nGPU prepay option. The sources look perfectly plausible. This is why \"it cites sources\" is not the\nsame as \"it's grounded.\"</p>\n<p>Two fixes, both already in the code above:</p>\n<ol>\n<li class=\"\"><strong>Structure-aware extraction</strong> — <code>get_text(\"\\n\", strip=True)</code> keeps line boundaries, so price\ncards stay separate lines instead of one soup.</li>\n<li class=\"\"><strong>A hardened generation prompt</strong> — quote numbers verbatim with unit and item; never combine\nlines; never derive new numbers from examples. (Before this rule, the model happily <em>computed</em>\na fake \"$1.5834/GPU-hour\" from a daily-spend example.)</li>\n</ol>\n<p>After re-ingesting with the fix, the storage-price conflation is gone. Residual ambiguity remains —\nthe pricing strip still contains fragments like \"$1.44/mo\" (a compute-instance price) near the GPU\nline, and truly fixing that needs table-aware extraction (see <em>What's next</em>). Which is exactly why\nthe next step exists: <strong>pin the fixed bug so it can never quietly return.</strong></p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"step-5--eval-the-service-like-a-ci-gate\">Step 5 — Eval the service like a CI gate<a href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/#step-5--eval-the-service-like-a-ci-gate\" class=\"hash-link\" aria-label=\"Enlace directo al Step 5 — Eval the service like a CI gate\" title=\"Enlace directo al Step 5 — Eval the service like a CI gate\" translate=\"no\">​</a></h2>\n<p>This step took <strong>four rounds of fixes to get an honest number</strong> — and the journey from 39% to 87%\nis the best lesson in the series, because almost nothing we fixed along the way was the RAG itself.</p>\n<p>We reuse part 3's skill — generate a QA dataset <em>from the corpus itself</em> — and grade the <strong>live\nHTTP endpoint</strong> with Promptfoo. The first generator version asked for one question per page with a\n<code>key_fact</code> the answer must contain, and got 37/37 pairs back. Some labels already smelled — a vague\n<code>\"All paid\"</code>, menu paths instead of answers — but let's run it naively first and let the failures\nteach us. Convert to a Promptfoo dataset with plain substring assertions (<code>icontains:</code> + the key\nfact) and point it at the running service. The provider is <strong>HTTP</strong> — we grade the real API,\nexactly what a CI gate would do:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">mkdir</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-p</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">eval</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">echo</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'question,__expected'</span><span class=\"token punctuation\" style=\"color:#393A34\">;</span><span class=\"token plain\"> jq </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-r</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'[.question, (\"icontains:\" + .key_fact)] | @csv'</span><span class=\"token plain\"> app/qa.jsonl</span><span class=\"token punctuation\" style=\"color:#393A34\">;</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> eval/tests.csv</span><br></div></code></pre></div></div>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">cat</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> eval/promptfooconfig.yaml </span><span class=\"token operator\" style=\"color:#393A34\">&lt;&lt;</span><span class=\"token string\" style=\"color:#e3116c\">'EOF'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">description: \"RAG service eval — QA set generated from the scraped docs + regression\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">providers:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - id: https</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    config:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      url: http://localhost:8000/ask</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      method: POST</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      headers:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        Content-Type: application/json</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      body:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        question: \"{{question}}\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      transformResponse: json.answer</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">tests:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - file://tests.csv</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  # regression: the storage-price-as-GPU-price conflation from Step 4</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - vars:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      question: \"What is the pricing model for GPU hourly usage in Edge Cloud?\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    assert:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      - type: not-icontains</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        value: \"0.10\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      - type: not-icontains</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        value: \"GB-mo\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">EOF</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token builtin class-name\">cd</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">eval</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&amp;&amp;</span><span class=\"token plain\"> npx </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-y</span><span class=\"token plain\"> promptfoo@latest </span><span class=\"token builtin class-name\">eval</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> promptfooconfig.yaml --no-cache</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Output (development run — the service still had the Step-4 bug)</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Results:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  ✓ 15 passed (39.47%)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  ✗ 23 failed (60.53%)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Duration: 40s (concurrency: 4)</span><br></div></code></pre></div></div>\n<p>That 39.5% is what we measured <em>during development</em>, while the Step-4 bug was still live. Re-run\nthe same naive eval against the <strong>finished</strong> service and it lands much higher — 27/38:</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"The naive eval against the finished service — 71%\" src=\"https://wec.wiline.com/docs/es/assets/images/rag-eval-icontains-284e4ea4fa5a8f2dec99a5079c84c4f7.png\" width=\"827\" height=\"973\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figure 6.</strong> The same naive eval against the finished service: <strong>71.05%</strong>. Better service, same\nnoisy labels — the 30-point gap between these two runs is all service quality, and both numbers\nmean little until you read the rows.</p>\n<p><strong>Read the failures before you panic.</strong> From the development run's 23 failures, most are not RAG\nfailures:</p>\n<table><thead><tr><th>Failure</th><th>What actually happened</th></tr></thead><tbody><tr><td>\"Balance due\" answer: <em>\"the amount that remains due… $0.00\"</em></td><td>Correct answer; the label demanded the phrase <code>All paid</code> — <strong>bad label</strong></td></tr><tr><td>Payment Information: gives the exact right menu path</td><td>Correct; label phrasing doesn't substring-match — <strong>brittle assertion</strong></td></tr><tr><td>GPU pricing: <em>\"$0.10 per GB-month\"</em></td><td><strong>Real failure</strong> — the Step-4 conflation, reproducing systematically</td></tr><tr><td>TTS default format: <em>\"not specified in the context\"</em></td><td><strong>Real retrieval miss</strong> — the chunk with the default wasn't retrieved</td></tr></tbody></table>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"upgrade-1--a-judge-instead-of-substrings-it-barely-helps\">Upgrade 1 — a judge instead of substrings. It barely helps.<a href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/#upgrade-1--a-judge-instead-of-substrings-it-barely-helps\" class=\"hash-link\" aria-label=\"Enlace directo al Upgrade 1 — a judge instead of substrings. It barely helps.\" title=\"Enlace directo al Upgrade 1 — a judge instead of substrings. It barely helps.\" translate=\"no\">​</a></h3>\n<p>Substring matching (<code>icontains</code>) was perfect for part 2's single JSON field and is <strong>too brittle\nfor long-form RAG answers</strong>. The obvious upgrade is part 4's skill: an <strong>LLM judge</strong> grading\nsemantic consistency (<code>llm-rubric:</code> assertions + a judge model in <code>defaultTest</code>). We did exactly\nthat — same 37 labels, judge instead of substrings — and got:</p>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Output (judge, uncurated labels)</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Results:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  ✓ 17 passed (44.74%)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  ✗ 21 failed (55.26%)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Duration: 8m 21s (concurrency: 4)</span><br></div></code></pre></div></div>\n<p><strong>39.5% → 44.7%.</strong> Barely moved — because <strong>the judge faithfully enforces bad labels.</strong> Rows with\ngarbage <code>key_fact</code>s still fail: the reference is wrong, not the answer. A better grader cannot\nrescue a bad dataset. The bottleneck was never the assertion type.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"upgrade-2--curate-the-labels-with-a-gate\">Upgrade 2 — curate the labels with a gate<a href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/#upgrade-2--curate-the-labels-with-a-gate\" class=\"hash-link\" aria-label=\"Enlace directo al Upgrade 2 — curate the labels with a gate\" title=\"Enlace directo al Upgrade 2 — curate the labels with a gate\" translate=\"no\">​</a></h3>\n<p>So fix the data (part 3's discipline, now enforced by code). Generator v2 demands the label be\n<em>the answer</em> — not a menu path, not a section name — and adds a <strong>curation gate</strong>: the <code>key_fact</code>\nmust exist <strong>verbatim</strong> in the source text, or the row is rejected on the spot:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">cat</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> app/gen_qa.py </span><span class=\"token operator\" style=\"color:#393A34\">&lt;&lt;</span><span class=\"token string\" style=\"color:#e3116c\">'EOF'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">\"\"\"Generate a QA eval set from the indexed docs — v2, curated labels.\"\"\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">import os, json, chromadb</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">from openai import OpenAI</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">wec = OpenAI(base_url=\"https://inference.wiline.com/v1\",</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">             api_key=os.environ[\"WEC_API_KEY\"], timeout=60)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">db = chromadb.PersistentClient(path=\"/data\")</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">col = db.get_or_create_collection(\"wec-docs\")</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">data = col.get(include=[\"documents\", \"metadatas\"])</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">pages = {}</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">for doc, meta in zip(data[\"documents\"], data[\"metadatas\"]):</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    pages.setdefault(meta[\"url\"], []).append(doc)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">kept, skipped = 0, 0</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">with open(\"/srv/app/qa.jsonl\", \"w\") as f:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    for url, chunks in sorted(pages.items()):</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        ctx = \"\\n\".join(chunks[:2])[:2000]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        resp = wec.chat.completions.create(</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">            model=\"Qwen2.5-3B-Instruct\", temperature=0.3,</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">            messages=[{\"role\": \"user\", \"content\":</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">                \"From this documentation excerpt, write ONE question a customer would ask, \"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">                \"and the fact that ANSWERS it. Return ONLY JSON with keys:\\n\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">                \"question (string), key_fact (string).\\n\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">                \"Rules for key_fact: it must be THE ANSWER to the question (not a menu path, \"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">                \"not a section name), a short phrase copied VERBATIM from the excerpt, \"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">                \"max 8 words.\\n\\n\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">                f\"Excerpt:\\n{ctx}\"}],</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        )</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        raw = resp.choices[0].message.content</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        try:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">            start, end = raw.index(\"{\"), raw.rindex(\"}\") + 1</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">            row = json.loads(raw[start:end])</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">            # curation gate: the label must literally exist in the source text</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">            if row[\"key_fact\"].lower().strip() not in ctx.lower():</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">                print(f\"SKIP {url.split('/docs/')[1]}: key_fact not verbatim in source\")</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">                skipped += 1</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">                continue</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">            row[\"url\"] = url</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">            f.write(json.dumps(row) + \"\\n\")</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">            kept += 1</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">            print(f\"ok   {url.split('/docs/')[1]}: {row['question'][:50]} -&gt; {row['key_fact'][:40]}\")</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        except Exception as e:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">            print(f\"SKIP {url}: {e}\")</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">            skipped += 1</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">print(f\"done -&gt; {kept} kept, {skipped} rejected by curation gate\")</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">EOF</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose </span><span class=\"token builtin class-name\">exec</span><span class=\"token plain\"> rag-api python </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-m</span><span class=\"token plain\"> app.gen_qa</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Output (abridged)</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">ok   cloud_portal/management/billing/how_billing_works/: What is the starting point ... -&gt; How Billing Works</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">SKIP cloud_portal/management/billing/overview/: key_fact not verbatim in source</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">ok   cloud_portal/platform/inference/api/wiline-edge-cloud-inference-api/: What is the base URL ... -&gt; https://inference.wiline.com/v1</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">SKIP cloud_portal/platform/inference/api_keys/: key_fact not verbatim in source</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">...</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">done -&gt; 15 kept, 22 rejected by curation gate</span><br></div></code></pre></div></div>\n<p><strong>15 kept, 22 rejected.</strong> That rejection rate is the point: more than half of what the generator\nproduced would have graded the RAG against wrong references. A smaller, trustworthy dataset beats\na bigger, poisoned one.</p>\n<p>The generator samples at temperature 0.3, so the exact tally shifts slightly between runs — here's\na later regeneration that kept 16 of 37; the rejection rate stays brutal either way:</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"The curation gate — verbatim-verified pairs kept, the rest rejected\" src=\"https://wec.wiline.com/docs/es/assets/images/rag-qa-gen-8dfb631c47f8fc4cb9c506fef0f651d2.png\" width=\"946\" height=\"852\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figure 7.</strong> The gate at work: 16 kept, 21 rejected on this run. Every surviving label is provably\nin the docs — real answer-facts like <code>audio/mpeg</code> and the API base URL, not menu paths. (The\ndocumented eval runs below use our first curated set: 15 pairs + the regression = 16 tests.)</p>\n<p>Rebuild <code>tests.csv</code> with a rubric that gives the judge the question <strong>and</strong> the verified\nreference, plus explicit PASS/FAIL rules (different wording is fine; contradiction fails):</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">echo</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'question,__expected'</span><span class=\"token punctuation\" style=\"color:#393A34\">;</span><span class=\"token plain\"> jq </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-r</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'[.question, (\"llm-rubric:Question asked: \\\"\" + .question + \"\\\". Reference fact from the official docs: \\\"\" + .key_fact + \"\\\". PASS if the answer correctly addresses the question and does not contradict the reference fact — different wording is fine. FAIL only if the answer is wrong, contradicts the reference, or fails to answer the question.\")] | @csv'</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">..</span><span class=\"token plain\">/app/qa.jsonl</span><span class=\"token punctuation\" style=\"color:#393A34\">;</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> tests.csv</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Output (curated labels + judge)</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Results:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  ✓ 12 passed (75.00%)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  ✗ 4 failed (25.00%)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Duration: 2m 47s (concurrency: 4)</span><br></div></code></pre></div></div>\n<p><strong>75%.</strong> Better — but before celebrating, read the four failures. And here the series' habit pays\noff one more time, because <strong>three of them weren't RAG failures either.</strong></p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"upgrade-3--the-judge-itself-was-breaking\">Upgrade 3 — the judge itself was breaking<a href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/#upgrade-3--the-judge-itself-was-breaking\" class=\"hash-link\" aria-label=\"Enlace directo al Upgrade 3 — the judge itself was breaking\" title=\"Enlace directo al Upgrade 3 — the judge itself was breaking\" translate=\"no\">​</a></h3>\n<p>The three failures whose grader reason was just <em>\"No output\"</em> all shared two tells:\n<code>graderError: true</code>, and a completion of <strong>exactly 1024 tokens</strong> — Promptfoo's default\n<code>max_tokens</code>. Our judge is a thinking model: it spent the whole budget reasoning and got <strong>cut off\nbefore emitting the verdict JSON</strong>, which Promptfoo counts as FAIL. The fourth \"failure\" got a\nverdict — but the small judge had literally echoed the schema placeholder (<code>reason: \"string\"</code>)\ninstead of grading. Both answers were actually correct.</p>\n<p>Two config lines fix it — a bigger judge, and room to think:</p>\n<div class=\"language-yaml codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">eval/promptfooconfig.yaml (judge block, final)</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-yaml codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token key atrule\" style=\"color:#00a4db\">defaultTest</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">options</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">provider</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">id</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> openai</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\">chat</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\">Qwen3.5</span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">122B</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">config</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token key atrule\" style=\"color:#00a4db\">apiBaseUrl</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> https</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\">//inference.wiline.com/v1</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token key atrule\" style=\"color:#00a4db\">apiKeyEnvar</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> WEC_API_KEY</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token key atrule\" style=\"color:#00a4db\">temperature</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">0</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># thinking models spend tokens on reasoning before the verdict JSON;</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># the 1024 default silently truncated the judge and failed 3 tests</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        </span><span class=\"token key atrule\" style=\"color:#00a4db\">max_tokens</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">4096</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Output (final run)</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Results:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  ✓ 14 passed (87.50%)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  ✗ 2 failed (12.50%)</span><br></div></code></pre></div></div>\n<p>And the row that matters most:</p>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">│ What is the pricing model for GPU hourly usage in Edge Cloud?  │ [PASS] ... starting at $0.66/GPU-hour. │</span><br></div></code></pre></div></div>\n<p><strong>The regression is green</strong> — the Step-4 conflation is pinned and cannot quietly return. (One\ncosmetic note: the 122B judge sometimes keeps its reason text to a terse <code>...</code> — the verdicts\nthemselves are correct, which is what the gate reads.)</p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Skill unlocked 🏅</div><div class=\"admonitionContent_BuS1\"><p>You can gate a live RAG service in CI: generated + gate-curated QA, a judge for semantics,\ndeterministic regressions for known bugs — and you know how to debug the judge itself.</p></div></div>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"upgrade-4--the-875-didnt-survive-a-re-run\">Upgrade 4 — the 87.5% didn't survive a re-run<a href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/#upgrade-4--the-875-didnt-survive-a-re-run\" class=\"hash-link\" aria-label=\"Enlace directo al Upgrade 4 — the 87.5% didn't survive a re-run\" title=\"Enlace directo al Upgrade 4 — the 87.5% didn't survive a re-run\" translate=\"no\">​</a></h3>\n<p>One habit this series drills: <strong>re-run before you believe.</strong> We did — and got <strong>75%</strong>, with\n<em>different</em> rows failing than before. Billing, which had just passed, now failed with a circular\necho; the TTS answer said \"mp3\" one run and \"unspecified\" the next. The score was a lottery\nbecause <strong>the service itself was non-deterministic</strong>: <code>generate()</code> passed no <code>temperature</code>, so\nevery eval graded a different roll of the dice at the default 1.0.</p>\n<p>One line ends the lottery — add to the <code>create()</code> call in <code>generate()</code>:</p>\n<div class=\"language-python codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-python codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        temperature</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token number\" style=\"color:#36acaa\">0</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\">               </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># determinism: same question -&gt; same answer</span><br></div></code></pre></div></div>\n<p>Re-run twice. <strong>13/16 — 81.25% — both times, with the same three failures.</strong> The number went\n<em>down</em> and that's the win: 87.5% was partly luck (the flaky rows happened to land right that run);\n81.25% is reproducible. A reader who re-runs your eval should get your number.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"the-three-real-failures--finally-actual-rag-bugs\">The three real failures — finally, actual RAG bugs<a href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/#the-three-real-failures--finally-actual-rag-bugs\" class=\"hash-link\" aria-label=\"Enlace directo al The three real failures — finally, actual RAG bugs\" title=\"Enlace directo al The three real failures — finally, actual RAG bugs\" translate=\"no\">​</a></h3>\n<p>After four rounds of fixing the <em>measurement</em>, what's left is signal — stable across runs, and\neach one is a different <strong>classic RAG failure type</strong>:</p>\n<ol>\n<li class=\"\"><strong>Subnets (\"Total Networks\") — retrieval miss, chunk boundary.</strong> The right page ranks, but dump\nthe top-4 retrieved chunks and none contains the stats block with the answer; the top chunk\nstarts <strong>mid-word</strong> (<code>\"iated with subnets\\nData Transfer…\"</code>). Fixed-size character chunking\nsplit the fact across a boundary, and the half with the answer doesn't rank. Fix class: larger\noverlap, bigger <code>k</code>, or structure-aware chunking.</li>\n<li class=\"\"><strong>TTS default format — retrieval miss, fact not in top-k.</strong> The chunk stating the default never\nreaches the context. Revealing detail: at temperature 1.0 this row <em>sometimes passed</em> — the\nmodel guessed \"mp3\" without evidence. Determinism converted a lucky guess into an honest,\nconsistent failure.</li>\n<li class=\"\"><strong>Billing (\"How Billing Works\") — generation quality.</strong> At temperature 0 the small model\ndeterministically echoes the source sentence — <em>\"This is the starting point for billing…\"</em> —\nwithout naming the page. Correct refusal-free retrieval, useless circular answer; the judge\nrightly fails it. Fix class: prompt work (\"name the page/section you're citing\") or a stronger\ngeneration model.</li>\n</ol>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"spend-the-signal--one-parameter-chosen-by-the-eval\">Spend the signal — one parameter, chosen by the eval<a href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/#spend-the-signal--one-parameter-chosen-by-the-eval\" class=\"hash-link\" aria-label=\"Enlace directo al Spend the signal — one parameter, chosen by the eval\" title=\"Enlace directo al Spend the signal — one parameter, chosen by the eval\" translate=\"no\">​</a></h3>\n<p>A trustworthy eval isn't the goal; it's the <em>map</em>. It just told us two of three failures are the\nsame class — facts that don't reach the top-4 retrieved chunks. The cheapest fix in that class is\none character, in <code>retrieve()</code>:</p>\n<div class=\"language-python codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-python codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">retrieve</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">question</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token builtin\">str</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> k</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token builtin\">int</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">6</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\">     </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># was 4 — two misses said \"not enough context\"</span><br></div></code></pre></div></div>\n<p>Re-run. Then re-run again, because that's the habit now:</p>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Output (both runs)</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Results:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  ✓ 14 passed (87.50%)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  ✗ 2 failed (12.50%)</span><br></div></code></pre></div></div>\n<p><strong>14/16 — 87.5% — twice, same two failures.</strong> Read what happened with each:</p>\n<ul>\n<li class=\"\"><strong>TTS default — fixed.</strong> With six chunks the page stating the default reaches the context, and\nthe service now answers \"mp3\" <em>with evidence</em>, every run — the lucky guess became a grounded\nanswer.</li>\n<li class=\"\"><strong>Subnets — survived.</strong> Even at <code>k=6</code> the chunk with the stats block doesn't rank: this miss is\n<em>semantic</em>, not budgetary — the boundary-mangled text (<code>\"iated with subnets…\"</code>) embeds poorly.\nMore <code>k</code> can't fix what chunking broke; this one genuinely needs structure-aware chunking.</li>\n<li class=\"\"><strong>Billing — survived, as predicted.</strong> It's a generation-quality failure; no retrieval parameter\nwas ever going to touch it.</li>\n</ul>\n<p>The number is back to 87.5% — the same as the lucky run — <strong>but this time it reproduces</strong>, and\nevery point of it has an explanation.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Final eval — deterministic service, k=6, judge-graded, regression passing\" src=\"https://wec.wiline.com/docs/es/assets/images/rag-eval-final-eb3776d3a5abed5af34d6896bbee06d0.png\" width=\"965\" height=\"976\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figure 8.</strong> The final run: 14/16, the same two failures every time, regression PASS.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"read-the-final-number-honestly\">Read the final number honestly<a href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/#read-the-final-number-honestly\" class=\"hash-link\" aria-label=\"Enlace directo al Read the final number honestly\" title=\"Enlace directo al Read the final number honestly\" translate=\"no\">​</a></h3>\n<p>39.5% → 44.7% → 75% → 87.5% <em>(lucky)</em> → 81.25% <em>(honest)</em> → <strong>87.5% (earned)</strong>. Until the very\nlast step, the RAG barely changed — what changed was the <strong>measurement</strong>: assertions (substrings →\njudge), labels (curation gate), the judge's own config (truncation), determinism (temperature).\nOnly then did one retrieval parameter, chosen <em>by</em> the eval, actually move quality. That's the\nlast lesson of the series:</p>\n<ul>\n<li class=\"\"><strong>Most \"failures\" were our eval lying to us</strong> — bad labels, brittle asserts, a silently\ntruncated judge, a dice-rolling service. Read the rows before you touch the RAG.</li>\n<li class=\"\"><strong>The honest number was lower than the lucky one — and worth more</strong>, because it reproduces. If\nyour score changes when you re-run, you don't have a score yet.</li>\n<li class=\"\"><strong>Fix the measurement first, then spend its signal.</strong> The same 87.5% appears twice in this arc;\nonly the second one means something — it survives re-runs and every point has an explanation.</li>\n<li class=\"\"><strong>The score is a trend instrument; the rows are the truth.</strong> 87.5% still isn't \"the RAG's\nquality\" — it's this dataset, this judge, this corpus. Pin what must never regress with\ndeterministic asserts; let the judge track the rest.</li>\n</ul>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"what-you-built\">What you built<a href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/#what-you-built\" class=\"hash-link\" aria-label=\"Enlace directo al What you built\" title=\"Enlace directo al What you built\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\">A <strong>Docker-first RAG service</strong>: scrape (sitemap → article extraction) → chunk → <strong>local\nembeddings</strong> (fastembed/ONNX) → Chroma → <strong>WEC generation</strong>, behind <code>POST /ask</code> with\ndeterministic citations.</li>\n<li class=\"\">A <strong>real bug story</strong>: probed the negative case, caught a garbled GPU price (a storage rate welded\nonto the GPU prepay option), root-caused it to the scraper's flattened structure, fixed ingestion<!-- -->\n<ul>\n<li class=\"\">hardened the prompt, and <strong>pinned it with a regression test that now passes</strong>.</li>\n</ul>\n</li>\n<li class=\"\">A <strong>CI-shaped eval</strong> of the live endpoint: gate-curated QA (15 verbatim-verified pairs out of 37\ngenerated) + a judge you debugged like any other component — 39.5% → <strong>87.5% reproducible</strong>, by\nfixing the <em>measurement</em> first (assertions, labels, judge config, determinism) and then spending\nits signal on one eval-chosen retrieval parameter. Two real, nameable failures remain — on\npurpose, with fix classes named.</li>\n<li class=\"\"><strong>Full observability</strong>: every request a span tree in Langfuse — retrieval and generation\nseparately debuggable.</li>\n</ul>\n<p>Parts 1–4 were the toolkit. This is what the toolkit is <em>for</em>.</p>\n<div class=\"skillComplete\"><span class=\"skillComplete__medal\" aria-hidden=\"true\">🎯</span><div class=\"skillComplete__text\"><div class=\"skillComplete__title\">Finished this tutorial?</div><div class=\"skillComplete__sub\">Mark it complete to earn <strong>RAG, end to end</strong> on your skill path.</div></div><button type=\"button\" class=\"skillComplete__btn\">Mark as complete ✓</button></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"troubleshooting\">Troubleshooting<a href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/#troubleshooting\" class=\"hash-link\" aria-label=\"Enlace directo al Troubleshooting\" title=\"Enlace directo al Troubleshooting\" translate=\"no\">​</a></h2>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"ask-returns-nothing-after-re-indexing\"><code>/ask</code> returns nothing after re-indexing<a href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/#ask-returns-nothing-after-re-indexing\" class=\"hash-link\" aria-label=\"Enlace directo al ask-returns-nothing-after-re-indexing\" title=\"Enlace directo al ask-returns-nothing-after-re-indexing\" translate=\"no\">​</a></h3>\n<p>If you drop and recreate the Chroma collection while the API is running, the service holds a\n<strong>stale collection handle</strong> and every query throws:</p>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">chromadb.errors.InvalidCollectionException: Collection 9b486740-… does not exist.</span><br></div></code></pre></div></div>\n<p>The running process cached the old collection's UUID. Restart the service\n(<code>docker compose restart rag-api</code>) — or resolve the collection per-request instead of at startup.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"The stale-collection error after re-indexing\" src=\"https://wec.wiline.com/docs/es/assets/images/rag-stale-handle-1639bfb9ef1938577ff551daa191a889.png\" width=\"942\" height=\"529\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figure 9.</strong> Re-indexing invalidates open handles — a classic \"works until you rebuild the index\"\nproduction trap.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"failed-to-send-telemetry-event--capture-takes-1-positional-argument\"><code>Failed to send telemetry event … capture() takes 1 positional argument</code><a href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/#failed-to-send-telemetry-event--capture-takes-1-positional-argument\" class=\"hash-link\" aria-label=\"Enlace directo al failed-to-send-telemetry-event--capture-takes-1-positional-argument\" title=\"Enlace directo al failed-to-send-telemetry-event--capture-takes-1-positional-argument\" translate=\"no\">​</a></h3>\n<p>Harmless chromadb/posthog version clash. Set <code>ANONYMIZED_TELEMETRY: \"False\"</code> in the environment\n(already in our compose).</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"judge-failures-with-reason-no-output-and-gradererror-true\">Judge failures with reason \"No output\" (and <code>graderError: true</code>)<a href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/#judge-failures-with-reason-no-output-and-gradererror-true\" class=\"hash-link\" aria-label=\"Enlace directo al judge-failures-with-reason-no-output-and-gradererror-true\" title=\"Enlace directo al judge-failures-with-reason-no-output-and-gradererror-true\" translate=\"no\">​</a></h3>\n<p>Check the grading record's completion tokens: if it's <strong>exactly 1024</strong> — Promptfoo's default\n<code>max_tokens</code> — your thinking-model judge spent the whole budget reasoning and was cut off before\nthe verdict JSON. Set <code>max_tokens: 4096</code> on the judge provider. Related tell: a small judge\nreturning <code>reason: \"string\"</code> is echoing the schema placeholder instead of grading — use a bigger\njudge.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"the-judge-eval-is-slow-or-verdicts-look-random\">The judge eval is slow or verdicts look random<a href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/#the-judge-eval-is-slow-or-verdicts-look-random\" class=\"hash-link\" aria-label=\"Enlace directo al The judge eval is slow or verdicts look random\" title=\"Enlace directo al The judge eval is slow or verdicts look random\" translate=\"no\">​</a></h3>\n<p>Reasoning judges are slow (~minutes for a dozen rows) and imperfect — expect occasional verdict\nflips between runs. Keep <strong>deterministic assertions for known bugs</strong> (they're free and never flip)\nand treat the judge score as a trend, not a truth. If a judge row hangs entirely, it's usually a\ntransient network hiccup, not your config — retry before debugging.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"docker-build-fails-with-no-space-left-on-device\">Docker build fails with \"no space left on device\"<a href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/#docker-build-fails-with-no-space-left-on-device\" class=\"hash-link\" aria-label=\"Enlace directo al Docker build fails with &quot;no space left on device&quot;\" title=\"Enlace directo al Docker build fails with &quot;no space left on device&quot;\" translate=\"no\">​</a></h3>\n<p>The image needs ~1 GB (chromadb + onnxruntime are the heavy layers). Check <code>df -h /</code> first and\n<code>docker system df</code> for reclaimable space — but <strong>never prune blindly on a shared box</strong>: \"reclaimable\"\nlies when other projects' containers are running.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"whats-next\">What's next<a href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/#whats-next\" class=\"hash-link\" aria-label=\"Enlace directo al What's next\" title=\"Enlace directo al What's next\" translate=\"no\">​</a></h2>\n<p>The honest limitations are the roadmap, and each one traces to a failure you watched happen:\n<strong>structure-aware chunking</strong> (the subnets miss survived <code>k=6</code> — the boundary-mangled chunk embeds\npoorly, so no retrieval budget rescues it), <strong>prompt or model work on generation</strong> (the billing\necho: force the model to name the page it's citing, or use a stronger generator), <strong>table-aware\nextraction</strong> (the pricing strip is still fragment soup — the regression passes, but \"$1.44/mo\"\nstill bleeds near the GPU line; add a <em>positive</em> assert for the correct figure, not just\n<code>not-icontains</code> for the wrong ones), and <strong>judge calibration</strong> (grade the judge against a handful\nof human verdicts). Each one moves the pass rate for a reason you can name — which, after five\nparts, is the whole point: <em>you don't guess whether your AI works. You measure it, watch it, and\nmake it prove itself.</em></p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"further-reading\">Further reading<a href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/#further-reading\" class=\"hash-link\" aria-label=\"Enlace directo al Further reading\" title=\"Enlace directo al Further reading\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\"><a href=\"https://docs.trychroma.com/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Chroma docs — persistent client &amp; collections</a></li>\n<li class=\"\"><a href=\"https://github.com/qdrant/fastembed\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">fastembed — ONNX embeddings without PyTorch</a></li>\n<li class=\"\"><a href=\"https://www.promptfoo.dev/docs/providers/http/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Promptfoo — HTTP provider</a></li>\n<li class=\"\"><a href=\"https://langfuse.com/docs/sdk/python/decorators\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Langfuse — Python decorators</a></li>\n</ul>",
            "url": "https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/",
            "title": "The capstone: build, evaluate, and observe a RAG docs assistant on the WEC API",
            "summary": "Build a production-shaped RAG service in Docker: scrape a real docs site, embed locally, generate on the WEC Inference API — then catch a real hallucination, root-cause it to your own scraper, fix it, and pin it with a regression test. Every command, number, and error is real.",
            "date_modified": "2026-07-13T00:00:00.000Z",
            "author": {
                "name": "Rafael Fernandes",
                "url": "https://www.linkedin.com/in/rafaelmacariofernandes/"
            },
            "tags": [
                "ai",
                "rag",
                "embeddings",
                "chromadb",
                "docker",
                "evals",
                "promptfoo",
                "langfuse",
                "inference"
            ]
        },
        {
            "id": "https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/",
            "content_html": "<div class=\"tutorialHero\"><img class=\"tutorialHero__langfuse\" src=\"data:image/webp;base64,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\" alt=\"Langfuse\"><span class=\"tutorialHero__plus\">+</span><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"242\" height=\"255\" fill=\"none\" viewBox=\"0 0 242 255\" class=\"tutorialHero__wiline\"><path fill=\"#4E8EF7\" d=\"M56.711 11.293A195 195 0 0 1 41.899 4.79c4.788 1.85 9.71 4.028 14.812 6.504M73.807 29.35c-1.302-1.446-2.666-3.233-4.02-5.38 1.326 1.44 2.642 3.24 4.02 5.38M69.761 23.354c-.832-.585-1.752-1.472-2.558-2.714.9.567 1.685 1.49 2.558 2.714M62.97 15.774a10.7 10.7 0 0 1-3.158-1.841c.993.299 1.986.956 3.159 1.841M59.715 13.346c-.818-.194-1.782-.674-2.829-1.494.84.175 1.761.691 2.83 1.494M39.317 3.782c-.524.192-1.413.302-2.455.14.594-.255 1.342-.238 2.455-.14M74.906 31.339c-.334-.153-.73-.61-1.117-1.398.358.143.706.619 1.117 1.398M66.024 18.878c-.239.1-.557.05-.752-.276.35-.201.532-.069.752.276M71.773 150.625c3.363 7.291 7.185 14.411 9.983 21.914 4.474 11.997 11.167 20.832 24.296 24.757 10.62 3.175 20.369 1.795 30.093-1.816 9.937-3.689 15.165-11.978 18.501-21.541 4.349-12.466 9.385-24.443 19.668-33.446 11.32-9.909 24.639-14.788 39.429-15.63 8.3-.473 16.649-.081 25.444.444.472 15.673.385 30.825.55 45.974.038 3.499-1.049 4.719-4.557 4.543-4.982-.25-9.987-.079-14.98.009-.209.004-.4.991-.6 1.521-23.106 1.695-37.691 15.22-47.961 34.651-3.387 6.408-5.913 13.38-10.013 19.273-4.368 6.277-10.134 11.582-15.853 17.196-6.439 1.421-12.244 3.342-18.202 4.416-12.627 2.274-24.661.457-35.764-6.356-1.028-.631-2.395-.709-3.604-1.043 0 0-.117-.11-.047-.495-.818-.973-1.706-1.562-2.593-2.15 0 0 .09-.044.05-.335-.513-.545-.988-.798-1.463-1.052 0 0-.07-.123.012-.492-.561-.894-1.205-1.421-1.848-1.947 0 0 .134-.068.102-.28-.321-.442-.61-.671-.898-.901 0 0 .086-.007.074-.336-2.05-3.429-4.089-6.529-6.127-9.629 0 0 .094-.017.123-.365-3.013-6.117-6.056-11.886-9.1-17.655 0 0 .096-.01.088-.342-1.978-4.073-3.89-7.848-5.997-11.51-.246-.428-1.572-.235-2.398-.329 0 0-.226-.202-.114-.613-.213-.499-.537-.587-.861-.674 0 0-.032-.101.02-.455-.62-.846-1.291-1.339-1.964-1.832 0 0 .133-.083.119-.372-2.476-4.624-5.645-8.048-10.914-8.777 0 0 .108-.031.01-.326-1.673-1.444-3.105-2.918-4.85-3.688-9.751-4.308-20.013-5.643-30.63-5.23-7.305.285-7.316.051-7.317-7.446q-.002-18.496-.002-36.992v-3.57h36.106s-.08.019.133.283c1.43.415 2.646.567 3.863.719 0 0-.077.022.113.296 1.804.591 3.417.908 5.03 1.225 0 0 .139-.037.152.369 1.592.962 3.17 1.517 4.748 2.073 0 0-.091.027.052.267.735.415 1.328.59 1.92.765 0 0-.076.033.053.246.477.325.825.438 1.173.551 0 0-.149.018-.07.246.418.239.757.251 1.096.263 0 0 .23.058.189.465 1.522 1.319 3.084 2.231 4.647 3.143 0 0-.105.033-.015.316 2.723 2.764 5.356 5.244 7.989 7.724 0 0-.104.062-.094.352 1.01 1.401 2.01 2.512 3.01 3.624\"></path><path fill=\"#4E8EF7\" d=\"M214.77 52.819c-3.343.846-6.723 1.574-10.024 2.562-11.388 3.408-20.567 10.142-27.166 19.865-5.097 7.511-9.621 15.483-13.685 23.608-6.809 13.613-15.725 24.809-31.343 28.198-24.707 5.362-45.22-.126-57.852-24.564C69.466 92.363 63.8 82.375 57.349 73c-3.369-4.896-8.866-8.326-13.679-13.041-1.11-1.409-1.81-2.472-2.794-2.937-10.697-5.056-21.87-7.3-33.686-5.344-3.877.641-5.72-.297-5.615-4.86.294-12.816.26-25.646.019-38.464C1.509 3.874 3.15 2.5 7.429 2.716c9.774.492 19.564.678 29.347.989 0 0-.076.026.082.257 1.046.122 1.935.012 2.825-.098.575.043 1.15.086 2.038.692 5.265 2.72 10.218 4.877 15.17 7.034 0 0-.108.03-.016.317 1.058.765 2.022 1.245 2.987 1.724 0 0-.113.048-.082.354 1.144.876 2.258 1.446 3.372 2.017 0 0 .18.145.097.506.223.404.53.447.837.49 0 0 .214.13.144.504.244.426.558.477.873.528 0 0 .171.124.108.485.256.412.574.464.893.515 0 0 .167.127.105.493.265.437.592.509.919.58 0 0 .078.108.019.5.861 1.28 1.782 2.166 2.702 3.053 0 0-.101.061-.087.368 1.379 2.093 2.743 3.88 4.107 5.666 0 0-.094.011-.092.297.4.742.795 1.2 1.19 1.656 0 0-.121.02-.165.337.685 1.432 1.413 2.549 2.14 3.665 0 0-.11.024-.129.299.326.776.67 1.277 1.014 1.78 0 0-.084-.005-.1.367.933 4.079.311 8.497 4.648 10.67 4.69 12.164 12.107 21.794 25.852 23.497 19.618 2.431 35.321.062 45.299-24.554 3.398-8.383 7.099-16.902 12.232-24.264 8.353-11.979 21.046-17.795 35.832-20.32 1.666.417 2.649.651 3.624.622 9.93-.298 19.865-.513 29.785-1.032 3.818-.2 4.825 1.198 4.746 4.853-.218 9.967.004 19.944-.109 29.915-.052 4.583 1.149 10.606-1.239 13.302-2.044 2.308-8.394.86-12.842.967-3.82.092-7.644.036-11.467.045.065.31.097.633.213.922.034.084.306.072.469.105M37.585 127.354c-11.585.344-23.368.344-35.907.344v3.57q0 18.496.002 36.992c0 7.497.012 7.731 7.318 7.446 10.616-.413 20.878.922 30.63 5.23 1.743.77 3.176 2.244 4.84 3.744-13.655-7.873-28.93-7.767-44.468-7.398v-52.503c12.516.308 25.063-1.222 37.585 2.575\"></path><path fill=\"#4E8EF7\" d=\"M36.619 3.401c-9.626-.007-19.415-.193-29.19-.685C3.151 2.5 1.51 3.874 1.594 8.354c.241 12.818.275 25.648-.018 38.464-.105 4.563 1.737 5.501 5.614 4.86 11.816-1.956 22.99.288 33.686 5.344.984.465 1.685 1.529 2.616 2.66-13.39-7.538-28.284-7.343-43.479-6.78V0C12.314 1.045 24.388 2.071 36.62 3.401M215.232 52.852c-.625-.066-.897-.054-.931-.138-.116-.29-.148-.612-.212-.922 3.822-.01 7.646.047 11.467-.045 4.448-.108 10.797 1.341 12.842-.967 2.387-2.696 1.186-8.719 1.238-13.302.114-9.971-.109-19.948.109-29.915.08-3.655-.928-5.052-4.746-4.853-9.919.519-19.855.734-29.785 1.032-.975.03-1.958-.205-3.228-.58C214.559 1.945 227.423.991 240.73.003v52.882c-8.276 0-16.656 0-25.498-.033M220.071 177.398c-.271-.573-.08-1.561.129-1.565 4.993-.088 9.998-.259 14.98-.009 3.508.176 4.595-1.044 4.557-4.543-.165-15.149-.078-30.301-.088-45.911.458.372 1.32 1.202 1.324 2.036.079 16.424.059 32.848.059 50.036-6.984 0-13.737 0-20.961-.044M88.39 245.741c1.022.084 2.39.162 3.417.793 11.102 6.813 23.137 8.63 35.763 6.356 5.959-1.073 11.764-2.995 17.871-4.34-5.612 2.132-11.343 5.166-17.325 5.79-13.898 1.451-27.803 1.213-39.725-8.599M66.556 210.212c2.975 5.411 6.018 11.18 9.02 17.344-3.011-5.4-5.982-11.193-9.02-17.344M58.341 197.934c.666-.167 1.992-.36 2.238.068 2.107 3.662 4.018 7.437 5.985 11.557-2.69-3.535-5.376-7.449-8.223-11.625M44.637 185.241c5.099.438 8.268 3.862 10.737 8.564-3.518-2.513-7.043-5.393-10.737-8.564M82.29 48.422c-4.252-1.834-3.63-6.252-4.548-10.37 1.508 3.069 2.986 6.55 4.548 10.37M68.757 146.326c-2.533-2.157-5.166-4.637-7.882-7.455 2.539 2.151 5.16 4.641 7.882 7.455M75.532 228.226a122 122 0 0 1 6.047 9.319c-1.993-2.742-3.986-5.854-6.047-9.319M71.695 150.312c-.922-.799-1.921-1.91-2.906-3.366.952.788 1.89 1.921 2.906 3.366M46.663 130.007c-1.353-.103-2.966-.42-4.767-1.051 1.377.07 2.941.454 4.767 1.051M51.647 132.369a13.1 13.1 0 0 1-4.501-1.77c1.48.227 2.902.852 4.5 1.77M60.724 138.321a17.7 17.7 0 0 1-4.395-2.781c1.501.609 2.869 1.551 4.395 2.781M76.889 35.31c-.674-.781-1.402-1.898-2.068-3.39.711.768 1.362 1.912 2.068 3.39M85.668 243.124c.782.31 1.67.899 2.423 1.825-.862-.29-1.59-.919-2.423-1.825M41.596 128.372c-1.033.176-2.25.024-3.68-.432 1.023-.169 2.26-.032 3.68.432M55.367 194.37c.568.222 1.24.715 1.808 1.548-.637-.199-1.17-.738-1.808-1.548M82.397 239.295c.56.251 1.204.778 1.71 1.628-.633-.235-1.13-.794-1.71-1.628M84.291 241.679c.334.034.809.287 1.298.875-.376.004-.766-.326-1.298-.875M53.64 133.426c-.438.094-1.03-.081-1.764-.543.441-.101 1.026.086 1.764.543M77.805 37.409c-.322-.188-.666-.69-.978-1.52.34.182.648.693.978 1.52M81.588 238.108c.218-.039.507.19.823.707-.233.045-.493-.196-.823-.707M55.906 134.832c-.198.157-.537.145-.92-.161.262-.29.537-.195.92.161M57.276 196.552c.254-.079.578.009.756.361-.377.171-.557.018-.756-.361M54.888 134.25c-.215.129-.563.017-1.038-.351.217-.133.56-.012 1.038.351M64.014 16.858a.545.545 0 0 1-.71-.247c.344-.19.51-.067.71.247M65.027 17.882a.574.574 0 0 1-.734-.272c.35-.193.525-.063.734.272M67.045 19.939c-.244.093-.57.021-.777-.329.358-.192.55-.044.777.329\"></path></svg></div>\n<div class=\"skillTracker\" tabindex=\"0\" aria-label=\"Skill path: 0 of 8 skills earned\"><div class=\"skillTracker__ring\" style=\"--skillTracker-pct:0%\"><span class=\"skillTracker__ringLabel\">0<!-- -->/<!-- -->8</span></div><div class=\"skillTracker__panel\"><div class=\"skillTracker__header\"><span class=\"skillTracker__title\">🎯 Skill path</span><span class=\"skillTracker__count\">0<!-- -->/<!-- -->8<!-- --> earned</span></div><div class=\"skillTracker__series\">AI evals &amp; observability</div><ul class=\"skillTracker__steps\"><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">1</span><span class=\"skillTracker__skill\" data-state=\"locked\">Prove a model works</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">2</span><span class=\"skillTracker__skill\" data-state=\"locked\">Trustworthy JSON</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/synthetic-eval-datasets-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">3</span><span class=\"skillTracker__skill\" data-state=\"locked\">Real test data at scale</span></a></li><li><span class=\"skillTracker__step\"><span class=\"skillTracker__dot\" data-state=\"current\">4</span><span class=\"skillTracker__skill\" data-state=\"current\">Observe &amp; score production</span></span></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">5</span><span class=\"skillTracker__skill\" data-state=\"locked\">RAG, end to end</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/deepeval-regression-testing-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">6</span><span class=\"skillTracker__skill\" data-state=\"locked\">Catch regressions in CI</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/component-tracing-agent-tool-calls-wiline/\"><span class=\"skillTracker__dot\" data-state=\"locked\">7</span><span class=\"skillTracker__skill\" data-state=\"locked\">Trace &amp; debug agent tool calls</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/web-search-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">🏆</span><span class=\"skillTracker__skill\" data-state=\"locked\">Add live web search</span></a></li></ul><div class=\"skillTracker__footer\">Finish this tutorial, then mark it complete below to earn the skill.</div></div></div>\n<p>Un cliente dice que tu bot de soporte les prometió una política de reembolso que no existe. Tu función\nhizo <strong>dos</strong> llamadas LLM — clasificar, luego responder. ¿Cuál de ellas la inventó? Si no puedes responder eso,\ntu aplicación es una caja negra — y esta guía soluciona exactamente eso.</p>\n<p>Ahora puedes probar que un modelo funciona (<a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/\">parte 1</a>),\nhacer que su salida sea confiable para máquinas (<a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/\">parte 2</a>),\ny generar un conjunto de pruebas real para verificarlo\n(<a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/synthetic-eval-datasets-wiline-inference/\">parte 3</a>). Pero todo eso se ejecuta <strong>fuera de línea,\nen CI, con entradas que elegiste.</strong> La producción no juega de la misma manera.</p>\n<p>Esta guía cierra la brecha. Autoalojaremos <strong>Langfuse</strong> — la alternativa de código abierto y autoalojable\na LangSmith — rastrearemos cada llamada real, <strong>evaluaremos automáticamente el tráfico en vivo con un juez LLM</strong>,\nprofundizaremos en el paso exacto que falla, y <strong>retroalimentaremos las fallas</strong> para que tu conjunto de datos de la parte 3 se vuelva\nmás fuerte. La evaluación fuera de línea te dice que funcionó en tu conjunto de pruebas; esto te dice que funciona en el mundo real.</p>\n<!-- -->\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"la-habilidad-que-esto-añade\">La habilidad que esto añade<a href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/#la-habilidad-que-esto-a%C3%B1ade\" class=\"hash-link\" aria-label=\"Enlace directo al La habilidad que esto añade\" title=\"Enlace directo al La habilidad que esto añade\" translate=\"no\">​</a></h2>\n<p>Al final podrás tomar cualquier función WEC y: <strong>ver dentro de cada llamada, evaluar la calidad en tráfico real automáticamente, encontrar la llamada exacta que falló y por qué, y convertir fallas en producción en nuevos casos de prueba.</strong> Esa es la diferencia entre \"pasó CI\" y \"se mantiene en producción.\"</p>\n<p>Mantendremos el ejemplo en ejecución de la parte 3 — un <strong>clasificador de tickets de soporte</strong> en la API de Inferencia WEC — y lo pondremos bajo observación.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"lo-que-estamos-construyendo\">Lo que estamos construyendo<a href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/#lo-que-estamos-construyendo\" class=\"hash-link\" aria-label=\"Enlace directo al Lo que estamos construyendo\" title=\"Enlace directo al Lo que estamos construyendo\" translate=\"no\">​</a></h2>\n<p>Tu aplicación llama a la API WEC como de costumbre; Langfuse (autoalojado) captura silenciosamente <strong>cada llamada como un\nrastreo</strong>, un juez LLM <strong>evalúa</strong> cada una, y las fallas fluyen <strong>de vuelta a tu conjunto de datos de la parte 3</strong> — para que la producción siga fortaleciendo tus evaluaciones:</p>\n<!-- -->\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"por-qué-langfuse-y-cuándo-elegir-algo-más-ligero\">Por qué Langfuse (y cuándo elegir algo más ligero)<a href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/#por-qu%C3%A9-langfuse-y-cu%C3%A1ndo-elegir-algo-m%C3%A1s-ligero\" class=\"hash-link\" aria-label=\"Enlace directo al Por qué Langfuse (y cuándo elegir algo más ligero)\" title=\"Enlace directo al Por qué Langfuse (y cuándo elegir algo más ligero)\" translate=\"no\">​</a></h2>\n<p>Usamos Langfuse porque es el líder de código abierto en observabilidad LLM (con licencia MIT,\nautoalojable), su SDK es <strong>compatible con OpenAI</strong> por lo que apunta a la API WEC sin cambios, y — la\nrazón por la que encaja en <em>esta</em> serie — tiene <strong>conjuntos de datos, evaluación y evaluadores LLM-como-juez integrados.</strong>\nEso permite que tu conjunto de datos de la parte 3 y el bucle de \"evaluar producción, retroalimentar fallas\" sean\nde primera clase en lugar de añadidos.</p>\n<p>El compromiso: la pila de producción es pesada (Postgres + ClickHouse). Si no necesitas las características de evaluación\ny quieres algo más ligero, alternativas razonables son <strong>Arize Phoenix</strong> (nativa de OpenTelemetry,\nfuerte para evaluación fuera de línea), <strong>Helicone</strong> (un proxy de una línea — pero sin evaluaciones), o <strong>OpenLIT</strong>\n(un solo binario, nativa de OTEL). Para un flujo de trabajo impulsado por evaluaciones como este, los contenedores adicionales valen la pena; para un simple rastreo, uno de esos puede ser más adecuado para ti.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-1--autoalojar-langfuse-en-una-instancia-wec\">Paso 1 — Autoalojar Langfuse en una Instancia WEC<a href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/#paso-1--autoalojar-langfuse-en-una-instancia-wec\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 1 — Autoalojar Langfuse en una Instancia WEC\" title=\"Enlace directo al Paso 1 — Autoalojar Langfuse en una Instancia WEC\" translate=\"no\">​</a></h2>\n<p>La pila autoalojada de Langfuse consta de seis servicios (web + trabajador, ClickHouse, Postgres, Redis, almacenamiento de objetos), así que dale una Instancia WEC con <strong>~15 GB de disco y 4 GB+ de RAM</strong> — no lo metas en una máquina que ya esté ocupada.</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">git</span><span class=\"token plain\"> clone https://github.com/langfuse/langfuse.git </span><span class=\"token operator\" style=\"color:#393A34\">&amp;&amp;</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">cd</span><span class=\"token plain\"> langfuse</span><br></div></code></pre></div></div>\n<p>Genera tres secretos y colócalos en un <code>.env</code> (la <code>ENCRYPTION_KEY</code> <strong>debe</strong> ser hex, o el contenedor web no arrancará):</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">cat</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> .env </span><span class=\"token operator\" style=\"color:#393A34\">&lt;&lt;</span><span class=\"token string\" style=\"color:#e3116c\">EOF</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">NEXTAUTH_SECRET=</span><span class=\"token string variable\" style=\"color:#36acaa\">$(</span><span class=\"token string variable\" style=\"color:#36acaa\">openssl rand </span><span class=\"token string variable parameter variable\" style=\"color:#36acaa\">-base64</span><span class=\"token string variable\" style=\"color:#36acaa\"> </span><span class=\"token string variable number\" style=\"color:#36acaa\">32</span><span class=\"token string variable\" style=\"color:#36acaa\">)</span><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">SALT=</span><span class=\"token string variable\" style=\"color:#36acaa\">$(</span><span class=\"token string variable\" style=\"color:#36acaa\">openssl rand </span><span class=\"token string variable parameter variable\" style=\"color:#36acaa\">-base64</span><span class=\"token string variable\" style=\"color:#36acaa\"> </span><span class=\"token string variable number\" style=\"color:#36acaa\">32</span><span class=\"token string variable\" style=\"color:#36acaa\">)</span><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">ENCRYPTION_KEY=</span><span class=\"token string variable\" style=\"color:#36acaa\">$(</span><span class=\"token string variable\" style=\"color:#36acaa\">openssl rand </span><span class=\"token string variable parameter variable\" style=\"color:#36acaa\">-hex</span><span class=\"token string variable\" style=\"color:#36acaa\"> </span><span class=\"token string variable number\" style=\"color:#36acaa\">32</span><span class=\"token string variable\" style=\"color:#36acaa\">)</span><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">NEXTAUTH_URL=http://&lt;tu-ip-de-instancia&gt;:3000</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">EOF</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose up </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose </span><span class=\"token function\" style=\"color:#d73a49\">ps</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"La pila de Langfuse en funcionamiento — seis servicios saludables\" src=\"https://wec.wiline.com/docs/es/assets/images/langfuse-deploy-ps-597bc541cd07b60e848e6c0dfe6246fc.png\" width=\"2524\" height=\"422\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 1.</strong> <code>docker compose ps</code> — los seis servicios (web, trabajador, ClickHouse, Postgres, Redis, MinIO) en funcionamiento y saludables.</p>\n<div class=\"theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z\"></path></svg></span>Desplegando junto a otros servicios</div><div class=\"admonitionContent_BuS1\"><p>Si la máquina ya ejecuta algo en el puerto 3000 o 5432, Langfuse no arrancará (\"dirección ya en uso\"). Las bases de datos de respaldo no necesitan puertos de host en absoluto — edita <code>docker-compose.yml</code> para mover <code>langfuse-web</code> a un puerto de host libre (por ejemplo, <code>3001:3000</code>) y elimina el mapeo del host de Postgres. Mantener tus bases de datos fuera de la red del host es una buena práctica de todos modos. Consulta <a href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/#ts-ports\" class=\"\">Solución de problemas</a>.</p></div></div>\n<p>Abre <code>http://&lt;ip-de-instancia&gt;:3000</code>, crea una cuenta (el primer usuario es el propietario), luego una\n<strong>Organización → Proyecto</strong>, y bajo <strong>Configuración → Claves API</strong> crea un par de claves\n(<code>pk-lf-…</code> / <code>sk-lf-…</code>).</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-2--instrumentar-el-clasificador-para-que-cada-llamada-sea-rastreada\">Paso 2 — Instrumentar el clasificador para que cada llamada sea rastreada<a href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/#paso-2--instrumentar-el-clasificador-para-que-cada-llamada-sea-rastreada\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 2 — Instrumentar el clasificador para que cada llamada sea rastreada\" title=\"Enlace directo al Paso 2 — Instrumentar el clasificador para que cada llamada sea rastreada\" translate=\"no\">​</a></h2>\n<p>Apunta el SDK de Langfuse a tu instancia y usa su <strong>OpenAI drop-in</strong> — rastrea automáticamente cualquier\nllamada compatible con OpenAI, incluida la API de Inferencia WEC, sin código adicional alrededor de cada solicitud:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">python3 </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-m</span><span class=\"token plain\"> venv ~/lf-env </span><span class=\"token operator\" style=\"color:#393A34\">&amp;&amp;</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">source</span><span class=\"token plain\"> ~/lf-env/bin/activate</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">pip </span><span class=\"token function\" style=\"color:#d73a49\">install</span><span class=\"token plain\"> langfuse openai</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token builtin class-name\">export</span><span class=\"token plain\"> </span><span class=\"token assign-left variable\" style=\"color:#36acaa\">LANGFUSE_PUBLIC_KEY</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token string\" style=\"color:#e3116c\">\"pk-lf-…\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token builtin class-name\">export</span><span class=\"token plain\"> </span><span class=\"token assign-left variable\" style=\"color:#36acaa\">LANGFUSE_SECRET_KEY</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token string\" style=\"color:#e3116c\">\"sk-lf-…\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token builtin class-name\">export</span><span class=\"token plain\"> </span><span class=\"token assign-left variable\" style=\"color:#36acaa\">LANGFUSE_HOST</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token string\" style=\"color:#e3116c\">\"http://&lt;ip-de-instancia&gt;:3000\"</span><span class=\"token plain\">   </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># usa el puerto que expusiste — 3001 si lo remapeaste en el Paso 1</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token builtin class-name\">export</span><span class=\"token plain\"> </span><span class=\"token assign-left variable\" style=\"color:#36acaa\">WEC_API_KEY</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token string\" style=\"color:#e3116c\">\"sk-tu-clave-wec\"</span><br></div></code></pre></div></div>\n<div class=\"language-python codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">app.py</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-python codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> os</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">from</span><span class=\"token plain\"> langfuse</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">openai </span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> openai          </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># drop-in: rastrea automáticamente cada llamada</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">from</span><span class=\"token plain\"> langfuse </span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> get_client</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">client </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> openai</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">OpenAI</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    base_url</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token string\" style=\"color:#e3116c\">\"https://inference.wiline.com/v1\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    api_key</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\">os</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">environ</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"WEC_API_KEY\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    timeout</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token number\" style=\"color:#36acaa\">30</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\">                             </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># SIEMPRE establece esto — un backend lento cuelga para siempre de lo contrario</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">classify</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">ticket</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token builtin\">str</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> </span><span class=\"token builtin\">str</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    resp </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> client</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">chat</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">completions</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">create</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        model</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token string\" style=\"color:#e3116c\">\"Qwen2.5-3B-Instruct\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        name</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token string\" style=\"color:#e3116c\">\"ticket-classify\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\">             </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># nombra el rastreo en Langfuse</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        messages</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"role\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"user\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"content\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string\" style=\"color:#e3116c\">\"Clasifica este ticket de soporte. Devuelve SOLO JSON con claves \"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string\" style=\"color:#e3116c\">\"categoría (facturación/técnico/cuenta/otro), prioridad (baja/media/alta), \"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">f\"resumen. Ticket: </span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">ticket</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">\"</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">return</span><span class=\"token plain\"> resp</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">choices</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token number\" style=\"color:#36acaa\">0</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">message</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">content</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">if</span><span class=\"token plain\"> __name__ </span><span class=\"token operator\" style=\"color:#393A34\">==</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"__main__\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">print</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">classify</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"Me cobraron dos veces este mes.\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    get_client</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">flush</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\">                    </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># empuja los rastros antes de que el proceso termine</span><br></div></code></pre></div></div>\n<p>Ejecuta el código, luego abre <strong>Rastreo</strong> — la llamada aparecerá en segundos con entrada, salida, latencia, conteos de tokens, y (una vez que agreguemos precios) costo.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Un solo rastreo — entrada, salida, latencia y conteos de tokens\" src=\"https://wec.wiline.com/docs/es/assets/images/langfuse-first-trace-f013e891fddc5d65b3525d5e1ad59fb7.png\" width=\"2532\" height=\"1438\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 2.</strong> Una llamada <code>ticket-classify</code> en Langfuse: el prompt, la salida JSON, latencia (~1.2s),\ny conteos de tokens — capturada sin código adicional alrededor de la solicitud.</p>\n<div class=\"theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z\"></path></svg></span>Establecer un tiempo de espera</div><div class=\"admonitionContent_BuS1\"><p>El cliente de OpenAI <strong>no tiene un tiempo de espera predeterminado</strong>. Si el modelo es lento o el backend se detiene, tu\naplicación se cuelga indefinidamente — sin error, sin rastreo. <code>timeout=30</code> convierte eso en un fallo rápido y visible.</p></div></div>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"de-una-llamada-a-un-pipeline-real--el-árbol-de-spans\">De una llamada a un pipeline real — el árbol de spans<a href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/#de-una-llamada-a-un-pipeline-real--el-%C3%A1rbol-de-spans\" class=\"hash-link\" aria-label=\"Enlace directo al De una llamada a un pipeline real — el árbol de spans\" title=\"Enlace directo al De una llamada a un pipeline real — el árbol de spans\" translate=\"no\">​</a></h3>\n<p>Una llamada es fácil de registrar; no necesitas Langfuse para eso. El rastreo se justifica en funciones <strong>de múltiples pasos</strong>,\ndonde muestra <strong>toda la cadena como un solo árbol</strong> para que puedas ver <em>qué paso</em> falló. Hagamos\nque la función sea realista — <strong>clasifica el ticket, luego redacta una respuesta de esa categoría</strong> (dos llamadas WEC) —\ny agrúpalas en un solo rastreo con el decorador <code>@observe</code>:</p>\n<div class=\"language-python codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">pipeline.py</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-python codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token keyword\" style=\"color:#00009f\">from</span><span class=\"token plain\"> langfuse </span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> observe</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> get_client</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">from</span><span class=\"token plain\"> app </span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> client</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> classify              </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># reutiliza el cliente WEC rastreado + clasificador</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">draft_reply</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">ticket</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token builtin\">str</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> category</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token builtin\">str</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> </span><span class=\"token builtin\">str</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    resp </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> client</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">chat</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">completions</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">create</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        model</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token string\" style=\"color:#e3116c\">\"Qwen2.5-3B-Instruct\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        name</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token string\" style=\"color:#e3116c\">\"draft-reply\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        messages</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token string\" style=\"color:#e3116c\">\"role\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"user\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"content\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">            </span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">f\"Un cliente envió este ticket de </span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">category</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">. Escribe una respuesta corta y útil.\\nTicket: </span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">{</span><span class=\"token string-interpolation interpolation\">ticket</span><span class=\"token string-interpolation interpolation punctuation\" style=\"color:#393A34\">}</span><span class=\"token string-interpolation string\" style=\"color:#e3116c\">\"</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">return</span><span class=\"token plain\"> resp</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">choices</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token number\" style=\"color:#36acaa\">0</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">message</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">content</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token decorator annotation punctuation\" style=\"color:#393A34\">@observe</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\">                                     </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># agrupa todo lo que está debajo en UN solo rastreo</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">def</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">handle_ticket</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">ticket</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token builtin\">str</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">-</span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> </span><span class=\"token builtin\">str</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    category </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> classify</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">ticket</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\">                </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># child span 1</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    reply </span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\"> draft_reply</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">ticket</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> category</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\">      </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># child span 2</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">return</span><span class=\"token plain\"> reply</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">if</span><span class=\"token plain\"> __name__ </span><span class=\"token operator\" style=\"color:#393A34\">==</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"__main__\"</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">print</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">handle_ticket</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"Me cobraron dos veces este mes.\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    get_client</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">flush</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><br></div></code></pre></div></div>\n<p>Abre ese rastreo y obtendrás un <strong>árbol</strong>: <code>handle_ticket</code> → <code>ticket-classify</code> → <code>draft-reply</code>, cada\nhijo con su propia entrada, salida, latencia y tokens. Ahora un fallo tiene una <strong>dirección</strong> — puedes\nseñalar el paso exacto que falló en lugar de adivinar (usamos esto en el Paso 5).</p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Habilidad desbloqueada 🏅</div><div class=\"admonitionContent_BuS1\"><p>Ahora puedes <strong>ver dentro de cualquier llamada LLM en producción</strong> — entrada, salida, latencia, tokens — sin\nescribir una línea de código de registro.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-3--reproducir-tu-conjunto-de-datos-de-la-parte-3-a-través-de-ello\">Paso 3 — Reproducir tu conjunto de datos de la parte 3 a través de ello<a href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/#paso-3--reproducir-tu-conjunto-de-datos-de-la-parte-3-a-trav%C3%A9s-de-ello\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 3 — Reproducir tu conjunto de datos de la parte 3 a través de ello\" title=\"Enlace directo al Paso 3 — Reproducir tu conjunto de datos de la parte 3 a través de ello\" translate=\"no\">​</a></h2>\n<p>Para obtener una señal real (no una llamada), empuja tu <strong>conjunto de datos de la parte 3</strong> a través del clasificador para que el\ntablero tenga tráfico para analizar:</p>\n<div class=\"language-python codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">replay.py</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-python codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> os</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> json</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">from</span><span class=\"token plain\"> langfuse</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">openai </span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> openai</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">from</span><span class=\"token plain\"> langfuse </span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> get_client</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">from</span><span class=\"token plain\"> app </span><span class=\"token keyword\" style=\"color:#00009f\">import</span><span class=\"token plain\"> classify   </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># reutiliza la función rastreada</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">for</span><span class=\"token plain\"> line </span><span class=\"token keyword\" style=\"color:#00009f\">in</span><span class=\"token plain\"> </span><span class=\"token builtin\">open</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">os</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">path</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">expanduser</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token string\" style=\"color:#e3116c\">\"~/tickets_dataset.jsonl\"</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token keyword\" style=\"color:#00009f\">if</span><span class=\"token plain\"> line</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">strip</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">        classify</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">json</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">loads</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">line</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token string\" style=\"color:#e3116c\">\"ticket\"</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">get_client</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">.</span><span class=\"token plain\">flush</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><br></div></code></pre></div></div>\n<p>Ahora el <strong>Tablero de inicio</strong> muestra rastreos a lo largo del tiempo, una distribución de latencia (p50/p95), tokens por\nmodelo, y un total de costos — la vista de \"observa en producción\".</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El tablero de Langfuse poblado con tráfico reproducido\" src=\"https://wec.wiline.com/docs/es/assets/images/langfuse-scores-dashboard-e46938c8695864fa98dc8afa0e488b3a.png\" width=\"2526\" height=\"1432\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 3.</strong> Después de reproducir el conjunto de datos de la parte 3: rastreos a lo largo del tiempo, una distribución de latencia, tokens\npor modelo, y un total de costos — la vista de \"observa en producción\".</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"hacer-que-el-costo-sea-real-para-los-modelos-wec\">Hacer que el costo sea real para los modelos WEC<a href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/#hacer-que-el-costo-sea-real-para-los-modelos-wec\" class=\"hash-link\" aria-label=\"Enlace directo al Hacer que el costo sea real para los modelos WEC\" title=\"Enlace directo al Hacer que el costo sea real para los modelos WEC\" translate=\"no\">​</a></h3>\n<p>Langfuse calcula automáticamente el costo para los modelos que conoce (OpenAI, Anthropic), pero la API de Inferencia WEC\nno devuelve costo en dólares y Langfuse no conoce los precios de los modelos de WEC — así que el costo muestra <code>$0</code>. Esta es\nla misma brecha que encontramos en <a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/\">parte 1</a>. Soluciona esto una vez:\n<strong>Configuración → Modelos → agregar un modelo</strong> que coincida con <code>Qwen2.5-3B-Instruct</code> con tus precios por token de entrada/salida.\nAhora cada rastreo y el tablero muestran el gasto real — el seguimiento de costos que Promptfoo\nno pudo darnos.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-4--evaluar-automáticamente-el-tráfico-en-vivo-con-un-juez-llm\">Paso 4 — Evaluar automáticamente el tráfico en vivo con un juez LLM<a href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/#paso-4--evaluar-autom%C3%A1ticamente-el-tr%C3%A1fico-en-vivo-con-un-juez-llm\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 4 — Evaluar automáticamente el tráfico en vivo con un juez LLM\" title=\"Enlace directo al Paso 4 — Evaluar automáticamente el tráfico en vivo con un juez LLM\" translate=\"no\">​</a></h2>\n<p>La evaluación de CI verifica un conjunto fijo. Aquí evaluamos <strong>cada llamada real</strong> automáticamente con un LLM-como-juez.\nEste paso parece pura configuración — también es donde encontramos y solucionamos el mejor error de la\nserie.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"conectar-al-juez\">Conectar al juez<a href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/#conectar-al-juez\" class=\"hash-link\" aria-label=\"Enlace directo al Conectar al juez\" title=\"Enlace directo al Conectar al juez\" translate=\"no\">​</a></h3>\n<p>Crea el evaluador a través de <strong>Evaluadores → + Nuevo evaluador</strong>. Es un asistente de 3 pasos:</p>\n<p><strong>1. Seleccionar Evaluador</strong> — elige la plantilla de <strong>Correctitud</strong> LLM-como-juez.</p>\n<p><strong>2. Configurar conexión LLM</strong> — el juez necesita un modelo para llamar, y aún no hay ninguno, así que haz clic\n<strong>+ Agregar conexión LLM</strong>. El diálogo tiene más campos de los que esperarías — aquí está exactamente lo que necesita cada\nuno:</p>\n<ul>\n<li class=\"\"><strong>Adaptador LLM:</strong> <code>openai</code></li>\n<li class=\"\"><strong>Nombre del proveedor:</strong> cualquier etiqueta <em>sin dos puntos</em> (por ejemplo, <code>wec-judge</code>) — solo el nombre de la conexión en Langfuse</li>\n<li class=\"\"><strong>Clave API:</strong> tu clave WEC</li>\n<li class=\"\">Haz clic en <strong>Mostrar configuraciones avanzadas</strong>:<!-- -->\n<ul>\n<li class=\"\"><strong>URL base de la API:</strong> <code>https://inference.wiline.com/v1</code> — <strong>crítico</strong>; si se deja en blanco, golpea a OpenAI real</li>\n<li class=\"\"><strong>Usar API de Respuestas:</strong> deja <strong>apagado</strong> (WEC habla Completions de Chat)</li>\n<li class=\"\"><strong>Encabezados adicionales:</strong> ninguno — omitir</li>\n<li class=\"\"><strong>Habilitar modelos predeterminados:</strong> desactivar <strong>(no quieres la lista de modelos de OpenAI aquí)</strong></li>\n<li class=\"\"><strong>Modelos personalizados → Agregar nombre de modelo personalizado:</strong> <code>Qwen3.5:9B</code> — la elección del modelo importa mucho aquí, como verás</li>\n</ul>\n</li>\n<li class=\"\"><strong>Crear conexión</strong>, luego selecciona el proveedor <strong><code>wec-judge</code></strong> y el modelo <strong><code>Qwen3.5:9B</code></strong> → <strong>Guardar</strong>.</li>\n</ul>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El diálogo de Nueva Conexión LLM — URL base de WEC, modelos predeterminados apagados, un modelo WEC personalizado agregado\" src=\"https://wec.wiline.com/docs/es/assets/images/langfuse-llm-connection-e83545064c144db968575c7d85d0c809.png\" width=\"2520\" height=\"1274\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 4.</strong> El diálogo de Nueva Conexión LLM: adaptador <code>openai</code>, la URL base de la API WEC, modelos predeterminados apagados, y un modelo personalizado agregado — esto es lo que apunta al juez a WEC en lugar de\nOpenAI real.</p>\n<p><strong>3. Ejecutar Evaluador</strong> — agrega un filtro <strong>Nombre = <code>ticket-classify</code></strong>, activa <strong>Ejecutar en observaciones entrantes en vivo</strong> para que evalúe el nuevo tráfico, y <strong>Ejecutar</strong>.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"La configuración del evaluador LLM-como-juez\" src=\"https://wec.wiline.com/docs/es/assets/images/langfuse-evaluator-config-328cb01c6a061467eca223c1d7266d31.png\" width=\"2526\" height=\"1436\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 5.</strong> El evaluador de Correctitud — nombre de la evaluación, el filtro <code>ticket-classify</code>, y \"ejecutar en\nobservaciones entrantes en vivo.\"</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"luego-sin-puntuaciones-depurando-el-tiempo-de-espera-de-120-segundos\">Luego: sin puntuaciones. Depurando el tiempo de espera de 120 segundos<a href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/#luego-sin-puntuaciones-depurando-el-tiempo-de-espera-de-120-segundos\" class=\"hash-link\" aria-label=\"Enlace directo al Luego: sin puntuaciones. Depurando el tiempo de espera de 120 segundos\" title=\"Enlace directo al Luego: sin puntuaciones. Depurando el tiempo de espera de 120 segundos\" translate=\"no\">​</a></h3>\n<p>Configuramos todo, enviamos tráfico fresco… y nada. Sin puntuación en ningún rastreo. Así es como lo\nrastreamos — puedes encontrar la misma pared:</p>\n<ol>\n<li class=\"\"><strong>Verifica el registro del evaluador.</strong> <strong>Evaluadores → tu evaluador → Registros</strong> tenía el error real:\n<code>Request timed out after 120000ms</code>. Cada ejecución, exactamente 120s — así que el juez <em>sí</em> se estaba activando, su\nllamada LLM simplemente nunca regresó.</li>\n<li class=\"\"><strong>Sospecha del modelo.</strong> Nuestro primer juez, <code>Qwen2.5-3B-Instruct</code>, rechazó las llamadas a herramientas de inmediato\n(400 instantáneo — una configuración vLLM faltante en el backend; más sobre eso a continuación). Cambiamos a\n<code>Qwen3.5:9B</code>, que responde a una llamada directa a la herramienta en ~3s. <strong>Aún así, se agotó el tiempo.</strong></li>\n<li class=\"\"><strong>Sospecha de la red.</strong> <code>curl</code> desde el host: 200 en medio segundo. La misma solicitud de estilo juez desde <em>dentro del contenedor trabajador</em>: ~3s. Modelo, red, contenedor — todo bien.</li>\n<li class=\"\"><strong>Reproduce lo que Langfuse realmente envía.</strong> El juez no llama a la API como nuestro curl — utiliza <em>salida estructurada</em> (<code>response_format</code>) para que la puntuación regrese en un formato legible por máquina. Reproducir esa forma exacta de llamada expuso el problema: en este backend, <strong>las solicitudes de estilo juez al modelo de razonamiento son increíblemente lentas e impredecibles</strong> — la solicitud idéntica varió de 3 a 95 segundos en diferentes ejecuciones (el modelo \"piensa\" durante mucho tiempo, y el modo JSON añade sobrecarga). Con el prompt más largo del juez, las ejecuciones superaron el tiempo de espera predeterminado de 120s. <strong>Nada estaba roto — simplemente era más lento que el tiempo de espera.</strong></li>\n</ol>\n<p>La solución son dos líneas de configuración. Langfuse lee su tiempo de espera LLM de\n<code>LANGFUSE_FETCH_LLM_COMPLETION_TIMEOUT_MS</code> (predeterminado 120000). Dale espacio:</p>\n<div class=\"language-yaml codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">docker-compose.override.yml</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-yaml codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token key atrule\" style=\"color:#00a4db\">services</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">langfuse-worker</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">environment</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">LANGFUSE_FETCH_LLM_COMPLETION_TIMEOUT_MS</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"600000\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">langfuse-web</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">environment</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token key atrule\" style=\"color:#00a4db\">LANGFUSE_FETCH_LLM_COMPLETION_TIMEOUT_MS</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"600000\"</span><br></div></code></pre></div></div>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose up </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> langfuse-worker langfuse-web</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># verifica que se haya aplicado dentro del contenedor:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose </span><span class=\"token builtin class-name\">exec</span><span class=\"token plain\"> langfuse-worker </span><span class=\"token function\" style=\"color:#d73a49\">env</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">grep</span><span class=\"token plain\"> TIMEOUT</span><br></div></code></pre></div></div>\n<p>Envía un nuevo rastreo a través de <code>app.py</code>, espera un par de minutos, y:</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"La puntuación de Correctitud adjunta a un rastreo real\" src=\"https://wec.wiline.com/docs/es/assets/images/langfuse-score-on-trace-99653e359fac7e126dc097103602a4e5.png\" width=\"2520\" height=\"1424\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 6.</strong> Ahí está — <strong>Correctitud: 1.00</strong> adjunta a un rastreo <code>ticket-classify</code> en vivo, con\nel razonamiento del juez a un clic de distancia. Cada nueva llamada ahora se evalúa automáticamente.</p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Habilidad desbloqueada 🏅</div><div class=\"admonitionContent_BuS1\"><p>Ahora puedes <strong>evaluar automáticamente el tráfico real en producción</strong> — y depurar un evaluador que produce\nsilencio: verifica sus Registros, aísla modelo vs. red vs. contenedor, ajusta el tiempo de espera a la realidad.</p></div></div>\n<div class=\"theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z\"></path></svg></span>Dos restricciones a tener en cuenta</div><div class=\"admonitionContent_BuS1\"><p><strong>El juez necesita llamadas a herramientas.</strong> Si tu modelo devuelve un 400 en cualquier solicitud de <code>tools</code>, eso\ngeneralmente es la configuración de servicio, no el modelo — en vLLM son las banderas <code>--enable-auto-tool-choice</code> y\n<code>--tool-call-parser</code>. Ese fue nuestro caso: lo informamos, el equipo de la plataforma habilitó las\nbanderas en <code>Qwen2.5-3B-Instruct</code> el mismo día, y el 400 desapareció. Solo la conexión del juez se ve afectada — tu aplicación mantiene su modelo.</p><p><strong>La puntuación es lenta (~2 min/puntuación) y eso es el backend, no tú.</strong> El modelo de razonamiento \"piensa\"\ndurante mucho tiempo en los prompts del juez — vale la pena informarlo a tu equipo de plataforma (límites de presupuesto de razonamiento, o un backend de decodificación guiada como xgrammar en vLLM). El tiempo de espera elevado mantiene la puntuación confiable mientras tanto.</p></div></div>\n<div class=\"theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z\"></path></svg></span>Atajo tentador que no funciona: un modelo pequeño como juez</div><div class=\"admonitionContent_BuS1\"><p>Una vez que se habilitó la llamada a herramientas en <code>Qwen2.5-3B</code>, lo intentamos como juez — las puntuaciones regresaron en\n<strong>2–10 segundos</strong> en lugar de ~2 minutos. Pero eran basura: el mismo tipo de respuesta correcta obtuvo\n<strong>1, 1, 0, y 0.05</strong> en cuatro ejecuciones, con racionales casi idénticos adjuntos a puntuaciones opuestas. El modelo pequeño llena perfectamente el formato de puntuación; simplemente no puede <em>evaluar</em> realmente.\nImagen espejo del hallazgo de <a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/synthetic-eval-datasets-wiline-inference/\">parte 3</a>: el modelo pequeño gana en <em>clasificación</em> y pierde en <em>evaluación</em> — la evaluación es una tarea de razonamiento. Mantén el juez en <code>Qwen3.5:9B</code>: lento pero coherente.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-5--encontrar-la-falla-que-tus-pruebas-pasaron-por-alto\">Paso 5 — Encontrar la falla que tus pruebas pasaron por alto<a href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/#paso-5--encontrar-la-falla-que-tus-pruebas-pasaron-por-alto\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 5 — Encontrar la falla que tus pruebas pasaron por alto\" title=\"Enlace directo al Paso 5 — Encontrar la falla que tus pruebas pasaron por alto\" translate=\"no\">​</a></h2>\n<p>Aquí es donde el <strong>árbol de spans</strong> da sus frutos. Abre un rastreo <code>handle_ticket</code> y no obtienes un solo blob opaco — obtienes la <strong>cadena completa como un árbol</strong>, cada paso con su propia entrada, salida, latencia y\ntokens:</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Profundizando en un rastreo — el árbol de spans localiza cada paso\" src=\"https://wec.wiline.com/docs/es/assets/images/langfuse-failing-trace-8fcb77412fc10f743dad85e747382695.png\" width=\"2530\" height=\"1432\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 7.</strong> El árbol <code>handle_ticket</code> — <code>handle_ticket → ticket-classify → draft-reply</code> — con\nlatencia y tokens por paso. Cada paso tiene una dirección que puedes inspeccionar.</p>\n<p>Esa estructura es lo que hace que las fallas en producción sean depurables. Supongamos que un cliente recibe una respuesta sin sentido.\nSin rastreo solo sabrías \"la respuesta fue mala.\" Con el árbol lo expandes y lees cada paso — por ejemplo:</p>\n<ul>\n<li class=\"\"><strong><code>ticket-classify</code></strong> → <code>categoría: \"facturación\"</code> ✅ correcto — así que el clasificador no es el problema.</li>\n<li class=\"\"><strong><code>draft-reply</code></strong> → inventó una política de reembolso que no existe ❌ — <strong>el error está en el paso 2.</strong></li>\n</ul>\n<p>Has localizado la falla en el <strong>paso exacto, entrada y salida</strong> — deberías corregir el prompt de <code>draft-reply</code>,\nno perder tiempo en el clasificador. La evaluación fuera de línea te dice que la tasa de aprobación cayó; el rastreo\nte dice <em>qué llamada, con qué entrada, produjo qué salida incorrecta, en qué paso.</em> Y ahora que el Paso 4 evalúa cada llamada, ni siquiera tienes que buscar: <strong>filtra el Rastreo por puntuación baja</strong> y los rastreos sospechosos emergen por sí mismos — a menudo una redacción que tu conjunto de datos de la parte 3 nunca cubrió, que es exactamente lo que capturas de vuelta a continuación.</p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Habilidad desbloqueada 🏅</div><div class=\"admonitionContent_BuS1\"><p>Ahora puedes <strong>asignar una falla en producción al paso exacto, entrada y salida</strong> — no más adivinanzas\nsobre qué llamada inventó esa política de reembolso.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-6--cerrar-el-ciclo-producción--mejores-evaluaciones\">Paso 6 — Cerrar el ciclo: producción → mejores evaluaciones<a href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/#paso-6--cerrar-el-ciclo-producci%C3%B3n--mejores-evaluaciones\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 6 — Cerrar el ciclo: producción → mejores evaluaciones\" title=\"Enlace directo al Paso 6 — Cerrar el ciclo: producción → mejores evaluaciones\" translate=\"no\">​</a></h2>\n<p>Una falla en producción es un <strong>caso de prueba faltante</strong> — y Langfuse hace que capturarlo sea un clic. Crea\nel conjunto de datos una vez (<strong>Conjuntos de datos → + Nuevo conjunto de datos</strong>, por ejemplo, <code>fallas-en-producción</code>), luego en cualquier rastreo haz clic en <strong>+ Agregar a conjuntos de datos</strong> y selecciónalo. La entrada y salida del rastreo se completan automáticamente como un nuevo ítem:</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Agregando un rastreo de vuelta al conjunto de evaluación\" src=\"https://wec.wiline.com/docs/es/assets/images/langfuse-dataset-loop-9e09a4627451a945e4df5245e6018fe7.png\" width=\"2522\" height=\"1430\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 8.</strong> \"Agregar a conjuntos de datos\" convierte un rastreo de producción en un nuevo caso de prueba — entrada y salida esperada completadas automáticamente — alimentándolo directamente de vuelta a tu conjunto de datos de la parte 3.</p>\n<p>Para automatizarlo, extrae los rastreos que has marcado o agregado a un conjunto de datos a través del SDK de Langfuse, escribe\nfilas <code>{ticket, categoría, prioridad}</code> en <code>tickets_dataset.jsonl</code>, y vuelve a ejecutar la evaluación de Promptfoo de la parte 1/parte 3 — ahora cubre el caso del mundo real.</p>\n<p>Ahora tu evaluación de CI es más fuerte <em>porque</em> la producción encontró un hueco en ella. Ese es el ciclo:\n<strong>evaluación fuera de línea → enviar → observar y evaluar tráfico real → capturar fallas → la evaluación fuera de línea se vuelve\nmás fuerte.</strong></p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Habilidad desbloqueada 🏅</div><div class=\"admonitionContent_BuS1\"><p>Has cerrado el ciclo: <strong>las fallas en producción ahora hacen que tus evaluaciones sean más fuertes</strong> — el ciclo que\nmantiene una función de IA buena después de que se envía.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"lo-que-ahora-puedes-hacer\">Lo que ahora puedes hacer<a href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/#lo-que-ahora-puedes-hacer\" class=\"hash-link\" aria-label=\"Enlace directo al Lo que ahora puedes hacer\" title=\"Enlace directo al Lo que ahora puedes hacer\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\"><strong>Ver dentro de</strong> cada llamada WEC en producción (entrada/salida/latencia/costo).</li>\n<li class=\"\"><strong>Evaluar tráfico real automáticamente</strong> con un juez LLM — y depurar el evaluador mismo cuando se queda en silencio.</li>\n<li class=\"\"><strong>Depurar una falla específica</strong> hasta el paso exacto, no solo una métrica agregada.</li>\n<li class=\"\"><strong>Convertir fallas en producción en casos de prueba</strong>, para que tus evaluaciones sigan mejorando.</li>\n</ul>\n<p>Esa es la escalera de \"mantenerlo bien después del envío\". La evaluación fuera de línea (partes 1–3) prueba que funciona antes\ndel lanzamiento; esto prueba — y mantiene — que sigue funcionando después.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"solución-de-problemas\">Solución de problemas<a href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/#soluci%C3%B3n-de-problemas\" class=\"hash-link\" aria-label=\"Enlace directo al Solución de problemas\" title=\"Enlace directo al Solución de problemas\" translate=\"no\">​</a></h2>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"dirección-ya-en-uso-en-docker-compose-up\"><a id=\"ts-ports\"></a>\"dirección ya en uso\" en <code>docker compose up</code><a href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/#direcci%C3%B3n-ya-en-uso-en-docker-compose-up\" class=\"hash-link\" aria-label=\"Enlace directo al dirección-ya-en-uso-en-docker-compose-up\" title=\"Enlace directo al dirección-ya-en-uso-en-docker-compose-up\" translate=\"no\">​</a></h3>\n<p>Otro servicio ocupa el puerto 3000 (web) o 5432 (Postgres). Las bases de datos no necesitan puertos de host —\nen <code>docker-compose.yml</code>, remapea <code>langfuse-web</code> a un puerto libre (<code>3001:3000</code>) y elimina el mapeo del host de Postgres. Compose <em>fusiona</em> las listas de <code>ports</code>, así que un archivo de anulación con <code>ports: []</code> no las eliminará; edita el compose directamente.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"el-contenedor-web-no-arranca\">El contenedor web no arranca<a href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/#el-contenedor-web-no-arranca\" class=\"hash-link\" aria-label=\"Enlace directo al El contenedor web no arranca\" title=\"Enlace directo al El contenedor web no arranca\" translate=\"no\">​</a></h3>\n<p>Casi siempre <code>ENCRYPTION_KEY</code> — debe ser <code>openssl rand -hex 32</code> (64 caracteres hexadecimales), no base64.\nVerifica <code>docker compose logs langfuse-web</code>.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"el-costo-muestra-0\">El costo muestra <code>$0</code><a href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/#el-costo-muestra-0\" class=\"hash-link\" aria-label=\"Enlace directo al el-costo-muestra-0\" title=\"Enlace directo al el-costo-muestra-0\" translate=\"no\">​</a></h3>\n<p>Langfuse no tiene precio para los modelos de WEC por defecto. Agrega el modelo + precios por token bajo\n<strong>Configuración → Modelos</strong> (Paso 3).</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"no-aparecen-puntuaciones-en-nuevos-rastreos\">No aparecen puntuaciones en nuevos rastreos<a href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/#no-aparecen-puntuaciones-en-nuevos-rastreos\" class=\"hash-link\" aria-label=\"Enlace directo al No aparecen puntuaciones en nuevos rastreos\" title=\"Enlace directo al No aparecen puntuaciones en nuevos rastreos\" translate=\"no\">​</a></h3>\n<p>Verifica <strong>Evaluadores → tu evaluador → Registros</strong> primero — un error allí te dice que está ejecutándose y\nfallando, no inactivo. Luego empareja el error:</p>\n<ul>\n<li class=\"\"><strong>Un 400 mencionando llamadas a herramientas / <code>tool_choice</code></strong> — la llamada a herramientas no está habilitada para ese modelo en\nel backend de servicio (en vLLM: las banderas <code>--enable-auto-tool-choice</code> / <code>--tool-call-parser</code>).\nDos formas de salir: apunta la Conexión LLM del juez a un modelo donde funcione (<code>Qwen3.5:9B</code>), o pide\na tu equipo de plataforma que habilite las banderas — eso es lo que solucionó <code>Qwen2.5-3B-Instruct</code> para nosotros. De cualquier manera, solo cambia el juez — tu aplicación mantiene su modelo.</li>\n<li class=\"\"><strong><code>Request timed out after 120000ms</code></strong> — la llamada de salida estructurada del juez es más lenta que\nel tiempo de espera predeterminado de Langfuse. Aumenta <code>LANGFUSE_FETCH_LLM_COMPLETION_TIMEOUT_MS</code> (Paso 4) y\nconfirma con <code>docker compose exec langfuse-worker env | grep TIMEOUT</code>. Si <code>curl</code> simple es rápido\npero la puntuación aún se agota, no persigas la red — la ruta de salida estructurada es lo que es\nlento, y eso es una solución del backend.</li>\n</ul>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"las-puntuaciones-tardan-2-minutos-cada-una\">Las puntuaciones tardan ~2 minutos cada una<a href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/#las-puntuaciones-tardan-2-minutos-cada-una\" class=\"hash-link\" aria-label=\"Enlace directo al Las puntuaciones tardan ~2 minutos cada una\" title=\"Enlace directo al Las puntuaciones tardan ~2 minutos cada una\" translate=\"no\">​</a></h3>\n<p>Eso no es un colapso — es el modelo de razonamiento generando una larga cadena de pensamiento oculta en los prompts del juez (altamente variable: la misma solicitud puede tardar 3s o 95s), además de la sobrecarga de salida estructurada.\nAumentar el tiempo de espera hace que la puntuación sea <em>confiable</em> pero no <em>rápida</em> — la verdadera solución está del lado de la plataforma\n(límites de presupuesto de razonamiento, backend de decodificación guiada).</p>\n<div class=\"skillComplete\"><span class=\"skillComplete__medal\" aria-hidden=\"true\">🎯</span><div class=\"skillComplete__text\"><div class=\"skillComplete__title\">Finished this tutorial?</div><div class=\"skillComplete__sub\">Mark it complete to earn <strong>Observe &amp; score production</strong> on your skill path.</div></div><button type=\"button\" class=\"skillComplete__btn\">Mark as complete ✓</button></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"qué-sigue\">Qué sigue<a href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/#qu%C3%A9-sigue\" class=\"hash-link\" aria-label=\"Enlace directo al Qué sigue\" title=\"Enlace directo al Qué sigue\" translate=\"no\">​</a></h2>\n<p>Ahora puedes construir, probar, observar y mejorar una sola función WEC. La última escalera es el verdadero\npatrón de aplicación: <strong>RAG</strong> — recuperación + generación — con todo de las partes 1–4 integrado\n(salida verificada por esquema, un conjunto de datos de evaluación, y rastreo de Langfuse sobre todo el pipeline). Ahí es donde todo se une.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"lectura-adicional\">Lectura adicional<a href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/#lectura-adicional\" class=\"hash-link\" aria-label=\"Enlace directo al Lectura adicional\" title=\"Enlace directo al Lectura adicional\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\"><a href=\"https://langfuse.com/self-hosting\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Langfuse — documentación de autoalojamiento</a></li>\n<li class=\"\"><a href=\"https://langfuse.com/faq/all/best-phoenix-arize-alternatives\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Langfuse vs Arize Phoenix — compensaciones</a></li>\n<li class=\"\"><a href=\"https://posthog.com/blog/best-open-source-llm-observability-tools\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Herramientas de observabilidad LLM de código abierto comparadas (PostHog)</a></li>\n</ul>",
            "url": "https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/",
            "title": "Captura lo que tus pruebas no ven: observa y evalúa tu aplicación WEC en producción con Langfuse",
            "summary": "Las evaluaciones de CI pasan en un conjunto de pruebas fijo, pero la producción envía entradas que nunca probaste. Autoalojar Langfuse en una instancia WEC, rastrear cada llamada real, evaluar automáticamente el tráfico en vivo con un juez LLM, profundizar en el paso exacto que falló y retroalimentar las fallas para hacer tus evaluaciones más fuertes. Cada comando — y cada callejón sin salida — es real.",
            "date_modified": "2026-07-07T00:00:00.000Z",
            "author": {
                "name": "Rafael Fernandes",
                "url": "https://www.linkedin.com/in/rafaelmacariofernandes/"
            },
            "tags": [
                "ai",
                "observabilidad",
                "langfuse",
                "evals",
                "inferencia",
                "producción",
                "autoalojamiento"
            ]
        },
        {
            "id": "https://wec.wiline.com/docs/es/tutorials/synthetic-eval-datasets-wiline-inference/",
            "content_html": "<div class=\"tutorialHero\"><img class=\"tutorialHero__brand\" src=\"https://wec.wiline.com/docs/es/assets/images/promptfoo-avatar-31b32c657d72dac0a7edbd77c24ec717.jpg\" alt=\"Promptfoo\"><span class=\"tutorialHero__plus\">+</span><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"242\" height=\"255\" fill=\"none\" viewBox=\"0 0 242 255\" class=\"tutorialHero__wiline\"><path fill=\"#4E8EF7\" d=\"M56.711 11.293A195 195 0 0 1 41.899 4.79c4.788 1.85 9.71 4.028 14.812 6.504M73.807 29.35c-1.302-1.446-2.666-3.233-4.02-5.38 1.326 1.44 2.642 3.24 4.02 5.38M69.761 23.354c-.832-.585-1.752-1.472-2.558-2.714.9.567 1.685 1.49 2.558 2.714M62.97 15.774a10.7 10.7 0 0 1-3.158-1.841c.993.299 1.986.956 3.159 1.841M59.715 13.346c-.818-.194-1.782-.674-2.829-1.494.84.175 1.761.691 2.83 1.494M39.317 3.782c-.524.192-1.413.302-2.455.14.594-.255 1.342-.238 2.455-.14M74.906 31.339c-.334-.153-.73-.61-1.117-1.398.358.143.706.619 1.117 1.398M66.024 18.878c-.239.1-.557.05-.752-.276.35-.201.532-.069.752.276M71.773 150.625c3.363 7.291 7.185 14.411 9.983 21.914 4.474 11.997 11.167 20.832 24.296 24.757 10.62 3.175 20.369 1.795 30.093-1.816 9.937-3.689 15.165-11.978 18.501-21.541 4.349-12.466 9.385-24.443 19.668-33.446 11.32-9.909 24.639-14.788 39.429-15.63 8.3-.473 16.649-.081 25.444.444.472 15.673.385 30.825.55 45.974.038 3.499-1.049 4.719-4.557 4.543-4.982-.25-9.987-.079-14.98.009-.209.004-.4.991-.6 1.521-23.106 1.695-37.691 15.22-47.961 34.651-3.387 6.408-5.913 13.38-10.013 19.273-4.368 6.277-10.134 11.582-15.853 17.196-6.439 1.421-12.244 3.342-18.202 4.416-12.627 2.274-24.661.457-35.764-6.356-1.028-.631-2.395-.709-3.604-1.043 0 0-.117-.11-.047-.495-.818-.973-1.706-1.562-2.593-2.15 0 0 .09-.044.05-.335-.513-.545-.988-.798-1.463-1.052 0 0-.07-.123.012-.492-.561-.894-1.205-1.421-1.848-1.947 0 0 .134-.068.102-.28-.321-.442-.61-.671-.898-.901 0 0 .086-.007.074-.336-2.05-3.429-4.089-6.529-6.127-9.629 0 0 .094-.017.123-.365-3.013-6.117-6.056-11.886-9.1-17.655 0 0 .096-.01.088-.342-1.978-4.073-3.89-7.848-5.997-11.51-.246-.428-1.572-.235-2.398-.329 0 0-.226-.202-.114-.613-.213-.499-.537-.587-.861-.674 0 0-.032-.101.02-.455-.62-.846-1.291-1.339-1.964-1.832 0 0 .133-.083.119-.372-2.476-4.624-5.645-8.048-10.914-8.777 0 0 .108-.031.01-.326-1.673-1.444-3.105-2.918-4.85-3.688-9.751-4.308-20.013-5.643-30.63-5.23-7.305.285-7.316.051-7.317-7.446q-.002-18.496-.002-36.992v-3.57h36.106s-.08.019.133.283c1.43.415 2.646.567 3.863.719 0 0-.077.022.113.296 1.804.591 3.417.908 5.03 1.225 0 0 .139-.037.152.369 1.592.962 3.17 1.517 4.748 2.073 0 0-.091.027.052.267.735.415 1.328.59 1.92.765 0 0-.076.033.053.246.477.325.825.438 1.173.551 0 0-.149.018-.07.246.418.239.757.251 1.096.263 0 0 .23.058.189.465 1.522 1.319 3.084 2.231 4.647 3.143 0 0-.105.033-.015.316 2.723 2.764 5.356 5.244 7.989 7.724 0 0-.104.062-.094.352 1.01 1.401 2.01 2.512 3.01 3.624\"></path><path fill=\"#4E8EF7\" d=\"M214.77 52.819c-3.343.846-6.723 1.574-10.024 2.562-11.388 3.408-20.567 10.142-27.166 19.865-5.097 7.511-9.621 15.483-13.685 23.608-6.809 13.613-15.725 24.809-31.343 28.198-24.707 5.362-45.22-.126-57.852-24.564C69.466 92.363 63.8 82.375 57.349 73c-3.369-4.896-8.866-8.326-13.679-13.041-1.11-1.409-1.81-2.472-2.794-2.937-10.697-5.056-21.87-7.3-33.686-5.344-3.877.641-5.72-.297-5.615-4.86.294-12.816.26-25.646.019-38.464C1.509 3.874 3.15 2.5 7.429 2.716c9.774.492 19.564.678 29.347.989 0 0-.076.026.082.257 1.046.122 1.935.012 2.825-.098.575.043 1.15.086 2.038.692 5.265 2.72 10.218 4.877 15.17 7.034 0 0-.108.03-.016.317 1.058.765 2.022 1.245 2.987 1.724 0 0-.113.048-.082.354 1.144.876 2.258 1.446 3.372 2.017 0 0 .18.145.097.506.223.404.53.447.837.49 0 0 .214.13.144.504.244.426.558.477.873.528 0 0 .171.124.108.485.256.412.574.464.893.515 0 0 .167.127.105.493.265.437.592.509.919.58 0 0 .078.108.019.5.861 1.28 1.782 2.166 2.702 3.053 0 0-.101.061-.087.368 1.379 2.093 2.743 3.88 4.107 5.666 0 0-.094.011-.092.297.4.742.795 1.2 1.19 1.656 0 0-.121.02-.165.337.685 1.432 1.413 2.549 2.14 3.665 0 0-.11.024-.129.299.326.776.67 1.277 1.014 1.78 0 0-.084-.005-.1.367.933 4.079.311 8.497 4.648 10.67 4.69 12.164 12.107 21.794 25.852 23.497 19.618 2.431 35.321.062 45.299-24.554 3.398-8.383 7.099-16.902 12.232-24.264 8.353-11.979 21.046-17.795 35.832-20.32 1.666.417 2.649.651 3.624.622 9.93-.298 19.865-.513 29.785-1.032 3.818-.2 4.825 1.198 4.746 4.853-.218 9.967.004 19.944-.109 29.915-.052 4.583 1.149 10.606-1.239 13.302-2.044 2.308-8.394.86-12.842.967-3.82.092-7.644.036-11.467.045.065.31.097.633.213.922.034.084.306.072.469.105M37.585 127.354c-11.585.344-23.368.344-35.907.344v3.57q0 18.496.002 36.992c0 7.497.012 7.731 7.318 7.446 10.616-.413 20.878.922 30.63 5.23 1.743.77 3.176 2.244 4.84 3.744-13.655-7.873-28.93-7.767-44.468-7.398v-52.503c12.516.308 25.063-1.222 37.585 2.575\"></path><path fill=\"#4E8EF7\" d=\"M36.619 3.401c-9.626-.007-19.415-.193-29.19-.685C3.151 2.5 1.51 3.874 1.594 8.354c.241 12.818.275 25.648-.018 38.464-.105 4.563 1.737 5.501 5.614 4.86 11.816-1.956 22.99.288 33.686 5.344.984.465 1.685 1.529 2.616 2.66-13.39-7.538-28.284-7.343-43.479-6.78V0C12.314 1.045 24.388 2.071 36.62 3.401M215.232 52.852c-.625-.066-.897-.054-.931-.138-.116-.29-.148-.612-.212-.922 3.822-.01 7.646.047 11.467-.045 4.448-.108 10.797 1.341 12.842-.967 2.387-2.696 1.186-8.719 1.238-13.302.114-9.971-.109-19.948.109-29.915.08-3.655-.928-5.052-4.746-4.853-9.919.519-19.855.734-29.785 1.032-.975.03-1.958-.205-3.228-.58C214.559 1.945 227.423.991 240.73.003v52.882c-8.276 0-16.656 0-25.498-.033M220.071 177.398c-.271-.573-.08-1.561.129-1.565 4.993-.088 9.998-.259 14.98-.009 3.508.176 4.595-1.044 4.557-4.543-.165-15.149-.078-30.301-.088-45.911.458.372 1.32 1.202 1.324 2.036.079 16.424.059 32.848.059 50.036-6.984 0-13.737 0-20.961-.044M88.39 245.741c1.022.084 2.39.162 3.417.793 11.102 6.813 23.137 8.63 35.763 6.356 5.959-1.073 11.764-2.995 17.871-4.34-5.612 2.132-11.343 5.166-17.325 5.79-13.898 1.451-27.803 1.213-39.725-8.599M66.556 210.212c2.975 5.411 6.018 11.18 9.02 17.344-3.011-5.4-5.982-11.193-9.02-17.344M58.341 197.934c.666-.167 1.992-.36 2.238.068 2.107 3.662 4.018 7.437 5.985 11.557-2.69-3.535-5.376-7.449-8.223-11.625M44.637 185.241c5.099.438 8.268 3.862 10.737 8.564-3.518-2.513-7.043-5.393-10.737-8.564M82.29 48.422c-4.252-1.834-3.63-6.252-4.548-10.37 1.508 3.069 2.986 6.55 4.548 10.37M68.757 146.326c-2.533-2.157-5.166-4.637-7.882-7.455 2.539 2.151 5.16 4.641 7.882 7.455M75.532 228.226a122 122 0 0 1 6.047 9.319c-1.993-2.742-3.986-5.854-6.047-9.319M71.695 150.312c-.922-.799-1.921-1.91-2.906-3.366.952.788 1.89 1.921 2.906 3.366M46.663 130.007c-1.353-.103-2.966-.42-4.767-1.051 1.377.07 2.941.454 4.767 1.051M51.647 132.369a13.1 13.1 0 0 1-4.501-1.77c1.48.227 2.902.852 4.5 1.77M60.724 138.321a17.7 17.7 0 0 1-4.395-2.781c1.501.609 2.869 1.551 4.395 2.781M76.889 35.31c-.674-.781-1.402-1.898-2.068-3.39.711.768 1.362 1.912 2.068 3.39M85.668 243.124c.782.31 1.67.899 2.423 1.825-.862-.29-1.59-.919-2.423-1.825M41.596 128.372c-1.033.176-2.25.024-3.68-.432 1.023-.169 2.26-.032 3.68.432M55.367 194.37c.568.222 1.24.715 1.808 1.548-.637-.199-1.17-.738-1.808-1.548M82.397 239.295c.56.251 1.204.778 1.71 1.628-.633-.235-1.13-.794-1.71-1.628M84.291 241.679c.334.034.809.287 1.298.875-.376.004-.766-.326-1.298-.875M53.64 133.426c-.438.094-1.03-.081-1.764-.543.441-.101 1.026.086 1.764.543M77.805 37.409c-.322-.188-.666-.69-.978-1.52.34.182.648.693.978 1.52M81.588 238.108c.218-.039.507.19.823.707-.233.045-.493-.196-.823-.707M55.906 134.832c-.198.157-.537.145-.92-.161.262-.29.537-.195.92.161M57.276 196.552c.254-.079.578.009.756.361-.377.171-.557.018-.756-.361M54.888 134.25c-.215.129-.563.017-1.038-.351.217-.133.56-.012 1.038.351M64.014 16.858a.545.545 0 0 1-.71-.247c.344-.19.51-.067.71.247M65.027 17.882a.574.574 0 0 1-.734-.272c.35-.193.525-.063.734.272M67.045 19.939c-.244.093-.57.021-.777-.329.358-.192.55-.044.777.329\"></path></svg><span class=\"tutorialHero__plus\">+</span><img class=\"tutorialHero__brand\" src=\"data:image/png;base64,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\" alt=\"JSON\"></div>\n<div class=\"skillTracker\" tabindex=\"0\" aria-label=\"Skill path: 0 of 8 skills earned\"><div class=\"skillTracker__ring\" style=\"--skillTracker-pct:0%\"><span class=\"skillTracker__ringLabel\">0<!-- -->/<!-- -->8</span></div><div class=\"skillTracker__panel\"><div class=\"skillTracker__header\"><span class=\"skillTracker__title\">🎯 Skill path</span><span class=\"skillTracker__count\">0<!-- -->/<!-- -->8<!-- --> earned</span></div><div class=\"skillTracker__series\">AI evals &amp; observability</div><ul class=\"skillTracker__steps\"><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">1</span><span class=\"skillTracker__skill\" data-state=\"locked\">Prove a model works</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">2</span><span class=\"skillTracker__skill\" data-state=\"locked\">Trustworthy JSON</span></a></li><li><span class=\"skillTracker__step\"><span class=\"skillTracker__dot\" data-state=\"current\">3</span><span class=\"skillTracker__skill\" data-state=\"current\">Real test data at scale</span></span></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/\"><span class=\"skillTracker__dot\" data-state=\"locked\">4</span><span class=\"skillTracker__skill\" data-state=\"locked\">Observe &amp; score production</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">5</span><span class=\"skillTracker__skill\" data-state=\"locked\">RAG, end to end</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/deepeval-regression-testing-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">6</span><span class=\"skillTracker__skill\" data-state=\"locked\">Catch regressions in CI</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/component-tracing-agent-tool-calls-wiline/\"><span class=\"skillTracker__dot\" data-state=\"locked\">7</span><span class=\"skillTracker__skill\" data-state=\"locked\">Trace &amp; debug agent tool calls</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/web-search-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">🏆</span><span class=\"skillTracker__skill\" data-state=\"locked\">Add live web search</span></a></li></ul><div class=\"skillTracker__footer\">Finish this tutorial, then mark it complete below to earn the skill.</div></div></div>\n<p>Tu evaluación pasa el 100% — de los <strong>dos casos de prueba que escribiste a mano.</strong> Los usuarios reales no expresarán las cosas como tú lo hiciste.</p>\n<p>En <a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/\">parte 1</a> y\n<a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/\">parte 2</a> construimos un arnés de evaluación real —\nafirmaciones, validación de esquema, una matriz de modelos. Pero dos casos no pueden decirte si tu aplicación funciona;\nsolo pueden decirte que no se bloqueó con dos entradas.</p>\n<p>Esta guía soluciona eso. La habilidad es <strong>producir un conjunto de datos en el que realmente puedas confiar</strong>: usamos la\n<strong>API de Inferencia de WEC</strong> para <strong>generar tickets etiquetados</strong>, luego <strong>validarlos y curarlos</strong> — porque\nlas etiquetas generadas <em>no</em> son automáticamente correctas, y tratarlas como verdad solo mueve el error.\nEl resultado es datos de prueba reales a gran escala. Cuando finalmente lo ejecutes a través del arnés de la parte 2, la cobertura\nrevela las fallas — malas clasificaciones genuinas <em>y</em> etiquetas debatibles — que dos casos seleccionados a mano ocultan.</p>\n<!-- -->\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"cómo-se-integra-todo\">Cómo se integra todo<a href=\"https://wec.wiline.com/docs/es/tutorials/synthetic-eval-datasets-wiline-inference/#c%C3%B3mo-se-integra-todo\" class=\"hash-link\" aria-label=\"Enlace directo al Cómo se integra todo\" title=\"Enlace directo al Cómo se integra todo\" translate=\"no\">​</a></h2>\n<p>Cada paso alimenta al siguiente, terminando en un conjunto de datos que se conecta directamente a la evaluación de la parte 2:</p>\n<!-- -->\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"requisitos-previos\">Requisitos previos<a href=\"https://wec.wiline.com/docs/es/tutorials/synthetic-eval-datasets-wiline-inference/#requisitos-previos\" class=\"hash-link\" aria-label=\"Enlace directo al Requisitos previos\" title=\"Enlace directo al Requisitos previos\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\">\n<p>Una <strong>clave de API de Inferencia de WEC</strong> — consulta <a class=\"\" href=\"https://wec.wiline.com/docs/es/cloud_portal/platform/inference/api_keys/\">Inferencia → Claves de API</a>.</p>\n</li>\n<li class=\"\">\n<p><strong><code>jq</code></strong>, <strong><code>curl</code></strong>, y <strong>Node.js 22+</strong> (Promptfoo se ejecuta a través de <code>npx</code>).</p>\n</li>\n<li class=\"\">\n<p>Tu clave exportada y saneada (una clave pegada a menudo lleva caracteres invisibles):</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token builtin class-name\">export</span><span class=\"token plain\"> </span><span class=\"token assign-left variable\" style=\"color:#36acaa\">WEC_API_KEY</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token string\" style=\"color:#e3116c\">'sk-your-key'</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token builtin class-name\">export</span><span class=\"token plain\"> </span><span class=\"token assign-left variable\" style=\"color:#36acaa\">WEC_API_KEY</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token variable\" style=\"color:#36acaa\">$(</span><span class=\"token variable builtin class-name\" style=\"color:#36acaa\">printf</span><span class=\"token variable\" style=\"color:#36acaa\"> </span><span class=\"token variable string\" style=\"color:#e3116c\">'%s'</span><span class=\"token variable\" style=\"color:#36acaa\"> </span><span class=\"token variable string\" style=\"color:#e3116c\">\"</span><span class=\"token variable string variable\" style=\"color:#36acaa\">$WEC_API_KEY</span><span class=\"token variable string\" style=\"color:#e3116c\">\"</span><span class=\"token variable\" style=\"color:#36acaa\"> </span><span class=\"token variable operator\" style=\"color:#393A34\">|</span><span class=\"token variable\" style=\"color:#36acaa\"> </span><span class=\"token variable assign-left variable environment constant\" style=\"color:#36acaa\">LC_ALL</span><span class=\"token variable operator\" style=\"color:#393A34\">=</span><span class=\"token variable\" style=\"color:#36acaa\">C </span><span class=\"token variable function\" style=\"color:#d73a49\">tr</span><span class=\"token variable\" style=\"color:#36acaa\"> </span><span class=\"token variable parameter variable\" style=\"color:#36acaa\">-cd</span><span class=\"token variable\" style=\"color:#36acaa\"> </span><span class=\"token variable string\" style=\"color:#e3116c\">'[:print:]'</span><span class=\"token variable\" style=\"color:#36acaa\">)</span><br></div></code></pre></div></div>\n</li>\n</ul>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>tip</div><div class=\"admonitionContent_BuS1\"><p>Si una llamada devuelve un <code>401</code> / \"clave de API inválida\", tu clave ha expirado o ha sido revocada — genera una\nnueva en <strong>Inferencia → Claves de API</strong>. Consulta <a href=\"https://wec.wiline.com/docs/es/tutorials/synthetic-eval-datasets-wiline-inference/#ts-401\" class=\"\">Solución de problemas</a>.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-1--generar-un-lote-etiquetado\">Paso 1 — Generar un lote etiquetado<a href=\"https://wec.wiline.com/docs/es/tutorials/synthetic-eval-datasets-wiline-inference/#paso-1--generar-un-lote-etiquetado\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 1 — Generar un lote etiquetado\" title=\"Enlace directo al Paso 1 — Generar un lote etiquetado\" translate=\"no\">​</a></h2>\n<p>Pide al modelo tickets <strong>con sus respuestas</strong> — las etiquetas son lo que lo convierte en un <em>conjunto de datos</em>, no\nsolo entradas. Reutilizamos el esquema exacto de la parte 2 (<code>category</code> + <code>priority</code> enums):</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-s</span><span class=\"token plain\"> https://inference.wiline.com/v1/chat/completions </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Authorization: Bearer </span><span class=\"token string variable\" style=\"color:#36acaa\">$WEC_API_KEY</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Content-Type: application/json\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'{</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    \"model\": \"Qwen2.5-3B-Instruct\",</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    \"temperature\": 0.7,</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    \"messages\": [{\"role\":\"user\",\"content\":\"Genera 8 tickets de soporte al cliente diversos y realistas para una empresa de alojamiento en la nube. Devuelve SOLO JSONL (un objeto JSON por línea), cada uno con claves: ticket (cadena), category (uno de: billing, technical, account, other), priority (uno de: low, medium, high). Sin markdown, sin cercas.\"}]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  }'</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> jq </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-r</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'.choices[0].message.content'</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">{\"ticket\":\"Nuestro servidor se cayó inesperadamente y no estamos seguros de qué lo causó.\",\"category\":\"technical\",\"priority\":\"high\"}</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">{\"ticket\":\"Necesitamos actualizar nuestro plan pero el sistema muestra un mensaje de error al intentar cambiar de planes.\",\"category\":\"account\",\"priority\":\"medium\"}</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">{\"ticket\":\"Estamos teniendo problemas para acceder a nuestra base de datos y no podemos iniciar sesión.\",\"category\":\"technical\",\"priority\":\"high\"}</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">... 5 líneas más ...</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Paso 1 — una llamada devuelve JSONL limpio y etiquetado\" src=\"https://wec.wiline.com/docs/es/assets/images/dataset-gen-batch-adc9c63dcd4af4c2991e7581ea5908ec.png\" width=\"2524\" height=\"468\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 1.</strong> Una única generación: 8 tickets diversos, cada uno ya llevando su etiqueta de <code>category</code> y <code>priority</code>.</p>\n<p>JSONL limpio, etiquetas válidas, verdadera diversidad — y regresó en segundos. Elegimos deliberadamente un\n<strong>modelo pequeño y no razonador</strong> (<code>Qwen2.5-3B-Instruct</code>) para la generación. Aquí está el porqué de su importancia.</p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>¿Por qué no un modelo razonador para generación masiva?</div><div class=\"admonitionContent_BuS1\"><p>Los modelos razonadores devuelven su cadena de pensamiento en un campo separado <strong><code>reasoning_content</code></strong> — tokens\npor los que pagas pero que nunca usas. Pide a un modelo razonador como <code>Qwen3.5:9B</code> algunos tickets y compara el\nrazonamiento oculto con la respuesta real:</p><div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-s</span><span class=\"token plain\"> https://inference.wiline.com/v1/chat/completions </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Authorization: Bearer </span><span class=\"token string variable\" style=\"color:#36acaa\">$WEC_API_KEY</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Content-Type: application/json\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'{\"model\":\"Qwen3.5:9B\",\"temperature\":0.7,\"messages\":[{\"role\":\"user\",\"content\":\"Genera 3 tickets de soporte al cliente realistas para una empresa de alojamiento en la nube. Devuelve SOLO JSONL, claves: ticket, category, priority. Sin markdown, sin cercas.\"}]}'</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> jq </span><span class=\"token string\" style=\"color:#e3116c\">'{model, completion_tokens: .usage.completion_tokens, reasoning_chars: (.choices[0].message.reasoning_content|length), answer_chars: (.choices[0].message.content|length)}'</span><br></div></code></pre></div></div><div class=\"language-json codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-json codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token property\" style=\"color:#36acaa\">\"model\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Qwen3.5:9B\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token property\" style=\"color:#36acaa\">\"completion_tokens\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">2000</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token property\" style=\"color:#36acaa\">\"reasoning_chars\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">7116</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token property\" style=\"color:#36acaa\">\"answer_chars\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">412</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><br></div></code></pre></div></div><p><span class=\"zoomImage__wrap\"><img alt=\"La salida de un modelo razonador — el razonamiento oculto eclipsa la respuesta\" src=\"https://wec.wiline.com/docs/es/assets/images/dataset-gen-usage-5ee23a331ab19ce10dfddd509162743d.png\" width=\"2536\" height=\"370\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 1b.</strong> Para solo 3 tickets, <code>Qwen3.5:9B</code> escribió <strong>7,116 caracteres de razonamiento oculto para\nproducir 412 de respuesta</strong> — pagas por todos los ~2,000 tokens de finalización. (Los conteos varían por llamada; la\nasimetría no.) Por eso generamos con el pequeño y no razonador <code>Qwen2.5-3B</code> — y es la misma razón por la que esos modelos razonadores tropiezan con la salida estructurada en la matriz de la Parte 5.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-2--valida-antes-de-confiar-en-ello\">Paso 2 — Valida antes de confiar en ello<a href=\"https://wec.wiline.com/docs/es/tutorials/synthetic-eval-datasets-wiline-inference/#paso-2--valida-antes-de-confiar-en-ello\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 2 — Valida antes de confiar en ello\" title=\"Enlace directo al Paso 2 — Valida antes de confiar en ello\" translate=\"no\">​</a></h2>\n<p>Nunca trates los datos generados como verdad absoluta sin verificarlos. Guarda un lote y valida tres\ncosas: cada línea se analiza, cada etiqueta está en el enum, y las clases están razonablemente equilibradas.</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-s</span><span class=\"token plain\"> https://inference.wiline.com/v1/chat/completions </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Authorization: Bearer </span><span class=\"token string variable\" style=\"color:#36acaa\">$WEC_API_KEY</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Content-Type: application/json\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'{\"model\":\"Qwen2.5-3B-Instruct\",\"temperature\":0.7,\"messages\":[{\"role\":\"user\",\"content\":\"Genera 8 tickets de soporte al cliente diversos y realistas para una empresa de alojamiento en la nube. Devuelve SOLO JSONL (un objeto JSON por línea), cada uno con claves: ticket (cadena), category (uno de: billing, technical, account, other), priority (uno de: low, medium, high). Sin markdown, sin cercas.\"}]}'</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> jq </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-r</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'.choices[0].message.content'</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> tickets.jsonl</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># 1) ¿todas las líneas se analizan como JSON?</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">jq </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-e</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">.</span><span class=\"token plain\"> tickets.jsonl </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> /dev/null </span><span class=\"token operator\" style=\"color:#393A34\">&amp;&amp;</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">echo</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"todas las líneas son JSON válidos\"</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">||</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">echo</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"JSON INVÁLIDO presente\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># 2) ¿hay etiquetas fuera del enum? (sin filas impresas = todas válidas)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">jq </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'select((.category|IN(\"billing\",\"technical\",\"account\",\"other\")|not) or (.priority|IN(\"low\",\"medium\",\"high\")|not))'</span><span class=\"token plain\"> tickets.jsonl</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># 3) distribución de clases</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">jq </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-rs</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'group_by(.category)[] | \"\\(.[0].category): \\(length)\"'</span><span class=\"token plain\"> tickets.jsonl</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">todas las líneas son JSON válidos</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">account: 2</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">billing: 2</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">other: 1</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">technical: 3</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Paso 2 — validando el lote: JSON válidos, etiquetas en el enum, distribución de clases\" src=\"https://wec.wiline.com/docs/es/assets/images/dataset-validate-9b1201646866c2062c5e9aa3d093cfd2.png\" width=\"2534\" height=\"434\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 2.</strong> Salida de validación — cada línea se analiza, sin etiquetas fuera del enum, y una distribución de clases razonable.</p>\n<div class=\"theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z\"></path></svg></span>jq gotcha</div><div class=\"admonitionContent_BuS1\"><p>El intuitivo <code>[\"billing\",...] | index(.category)</code> <strong>falla</strong> con <em>\"No se puede indexar un array con\ncadena\"</em> — dentro del pipe, <code>.</code> es el array, así que <code>.category</code> intenta indexarlo. Usa\n<strong><code>.category | IN(\"billing\", ...)</code></strong> en su lugar. Consulta <a href=\"https://wec.wiline.com/docs/es/tutorials/synthetic-eval-datasets-wiline-inference/#ts-jq\" class=\"\">Solución de problemas</a>.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-3--escalar-y-eliminar-duplicados\">Paso 3 — Escalar y eliminar duplicados<a href=\"https://wec.wiline.com/docs/es/tutorials/synthetic-eval-datasets-wiline-inference/#paso-3--escalar-y-eliminar-duplicados\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 3 — Escalar y eliminar duplicados\" title=\"Enlace directo al Paso 3 — Escalar y eliminar duplicados\" translate=\"no\">​</a></h2>\n<p>Un lote de 8 no es suficiente. Repite la llamada varias veces (temperatura más alta para variedad entre\nlotes), concatena, y luego elimina duplicados por texto del ticket:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token keyword\" style=\"color:#00009f\">for</span><span class=\"token plain\"> </span><span class=\"token for-or-select variable\" style=\"color:#36acaa\">i</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:#00009f\">in</span><span class=\"token plain\"> </span><span class=\"token variable\" style=\"color:#36acaa\">$(</span><span class=\"token variable function\" style=\"color:#d73a49\">seq</span><span class=\"token variable\" style=\"color:#36acaa\"> </span><span class=\"token variable number\" style=\"color:#36acaa\">1</span><span class=\"token variable\" style=\"color:#36acaa\"> </span><span class=\"token variable number\" style=\"color:#36acaa\">5</span><span class=\"token variable\" style=\"color:#36acaa\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">;</span><span class=\"token plain\"> </span><span class=\"token keyword\" style=\"color:#00009f\">do</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-s</span><span class=\"token plain\"> https://inference.wiline.com/v1/chat/completions </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Authorization: Bearer </span><span class=\"token string variable\" style=\"color:#36acaa\">$WEC_API_KEY</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Content-Type: application/json\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'{\"model\":\"Qwen2.5-3B-Instruct\",\"temperature\":0.9,\"messages\":[{\"role\":\"user\",\"content\":\"Genera 8 tickets de soporte al cliente diversos y realistas para una empresa de alojamiento en la nube. Devuelve SOLO JSONL (un objeto JSON por línea), cada uno con claves: ticket (cadena), category (uno de: billing, technical, account, other), priority (uno de: low, medium, high). Sin markdown, sin cercas.\"}]}'</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> jq </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-r</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'.choices[0].message.content // empty'</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token builtin class-name\">echo</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"lote </span><span class=\"token string variable\" style=\"color:#36acaa\">$i</span><span class=\"token string\" style=\"color:#e3116c\"> hecho\"</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token file-descriptor important\">&amp;2</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">done</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> tickets_raw.jsonl</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># eliminar duplicados por texto del ticket</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">jq </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-sc</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'unique_by(.ticket)[]'</span><span class=\"token plain\"> tickets_raw.jsonl </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> tickets_dataset.jsonl</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token builtin class-name\">echo</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"crudo: </span><span class=\"token string variable\" style=\"color:#36acaa\">$(</span><span class=\"token string variable function\" style=\"color:#d73a49\">wc</span><span class=\"token string variable\" style=\"color:#36acaa\"> </span><span class=\"token string variable parameter variable\" style=\"color:#36acaa\">-l</span><span class=\"token string variable\" style=\"color:#36acaa\"> </span><span class=\"token string variable operator\" style=\"color:#393A34\">&lt;</span><span class=\"token string variable\" style=\"color:#36acaa\"> tickets_raw.jsonl</span><span class=\"token string variable\" style=\"color:#36acaa\">)</span><span class=\"token string\" style=\"color:#e3116c\">  →  deduplicado: </span><span class=\"token string variable\" style=\"color:#36acaa\">$(</span><span class=\"token string variable function\" style=\"color:#d73a49\">wc</span><span class=\"token string variable\" style=\"color:#36acaa\"> </span><span class=\"token string variable parameter variable\" style=\"color:#36acaa\">-l</span><span class=\"token string variable\" style=\"color:#36acaa\"> </span><span class=\"token string variable operator\" style=\"color:#393A34\">&lt;</span><span class=\"token string variable\" style=\"color:#36acaa\"> tickets_dataset.jsonl</span><span class=\"token string variable\" style=\"color:#36acaa\">)</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">crudo: 40  →  deduplicado: 40</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Paso 3 — cinco lotes concatenados y deduplicados a 40 filas únicas\" src=\"https://wec.wiline.com/docs/es/assets/images/dataset-scale-dedupe-74ac55864b3da488aa334816e11ede4a.png\" width=\"2524\" height=\"636\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 3.</strong> Escalando: 5 lotes → 40 filas después de la eliminación de duplicados exactos.</p>\n<p>Cero duplicados exactos en 5 lotes — una buena señal de que el modelo no se está repitiendo.</p>\n<div class=\"theme-admonition theme-admonition-caution admonition_xJq3 alert alert--warning\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z\"></path></svg></span>precaución</div><div class=\"admonitionContent_BuS1\"><p><code>unique_by(.ticket)</code> solo captura texto <strong>idéntico</strong>. Dos tickets \"el servidor está caído\" redactados de manera diferente son duplicados <em>semánticos</em> y se deslizarán — capturarlos requiere agrupamiento basado en incrustaciones, un paso más avanzado. Para un conjunto de datos inicial, la deduplicación exacta está bien.</p></div></div>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Protégete contra fallas silenciosas</div><div class=\"admonitionContent_BuS1\"><p>Nota el <strong><code>// empty</code></strong> en el filtro <code>jq</code>. Si una llamada falla (clave revocada, tiempo de espera del modelo), la\nrespuesta no tiene contenido y <code>.choices[0].message.content</code> es <code>null</code> — un simple <code>jq -r</code> escribiría la palabra literal <code>null</code> en tu conjunto de datos, envenenándolo silenciosamente. <code>// empty</code> elimina esos,\nasí que una llamada fallida agrega <strong>nada</strong> en lugar de una fila basura. Siempre verifica el conteo de filas después.\nConsulta <a href=\"https://wec.wiline.com/docs/es/tutorials/synthetic-eval-datasets-wiline-inference/#ts-null\" class=\"\">Solución de problemas</a>.</p></div></div>\n<p>Valida las 40 completas de la misma manera que en el Paso 2 (<code>jq -e .</code>, la verificación del enum, y ambas distribuciones).\nLas nuestras resultaron equilibradas: técnico 15 · cuenta 10 · facturación 10 · otro 5; prioridad alta 13 ·\nmedia 14 · baja 13.</p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Habilidad desbloqueada 🏅</div><div class=\"admonitionContent_BuS1\"><p>Ahora tienes un <strong>pipeline repetible para datos de prueba reales</strong> — 40 filas etiquetadas, validadas y deduplicadas\ndonde antes tenías dos.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-4--convertir-a-un-conjunto-de-datos-de-promptfoo\">Paso 4 — Convertir a un conjunto de datos de Promptfoo<a href=\"https://wec.wiline.com/docs/es/tutorials/synthetic-eval-datasets-wiline-inference/#paso-4--convertir-a-un-conjunto-de-datos-de-promptfoo\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 4 — Convertir a un conjunto de datos de Promptfoo\" title=\"Enlace directo al Paso 4 — Convertir a un conjunto de datos de Promptfoo\" translate=\"no\">​</a></h2>\n<p>Promptfoo lee casos de prueba desde un CSV: cada columna se convierte en una variable, y la columna especial\n<strong><code>__expected</code></strong> contiene una afirmación por fila. Mapeamos <code>ticket</code> → la variable de prompt y\n<code>__expected</code> → <code>icontains:&lt;la categoría correcta&gt;</code>. <code>jq @csv</code> maneja las comas y comillas\ndentro del texto del ticket:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">echo</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'ticket,__expected'</span><span class=\"token punctuation\" style=\"color:#393A34\">;</span><span class=\"token plain\"> jq </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-r</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'[.ticket, (\"icontains:\" + .category)] | @csv'</span><span class=\"token plain\"> tickets_dataset.jsonl</span><span class=\"token punctuation\" style=\"color:#393A34\">;</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> tests.csv</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">wc</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-l</span><span class=\"token plain\"> tests.csv          </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># 41 = encabezado + 40 filas</span><br></div></code></pre></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-5--poner-el-conjunto-de-datos-a-trabajar\">Paso 5 — Poner el conjunto de datos a trabajar<a href=\"https://wec.wiline.com/docs/es/tutorials/synthetic-eval-datasets-wiline-inference/#paso-5--poner-el-conjunto-de-datos-a-trabajar\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 5 — Poner el conjunto de datos a trabajar\" title=\"Enlace directo al Paso 5 — Poner el conjunto de datos a trabajar\" translate=\"no\">​</a></h2>\n<p>El conjunto de datos es el entregable — todo desde aquí es lo que <em>desbloquea</em>. Primer beneficio: una comparación de modelos\nen la que realmente puedes confiar. En dos casos escritos a mano, un ranking es ruido; en 40 filas reales,\netiquetadas es una medición. Agrega cada modelo WEC como proveedor y ejecuta todo el conjunto:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">cat</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> promptfooconfig.yaml </span><span class=\"token operator\" style=\"color:#393A34\">&lt;&lt;</span><span class=\"token string\" style=\"color:#e3116c\">'EOF'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">description: \"Clasificador de tickets — matriz de modelos sobre el conjunto de datos sintético de 40 filas\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">providers:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - id: openai:chat:Qwen2.5-3B-Instruct</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    config: { apiBaseUrl: https://inference.wiline.com/v1, apiKeyEnvar: WEC_API_KEY, temperature: 0 }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - id: openai:chat:Qwen3.5:9B</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    config: { apiBaseUrl: https://inference.wiline.com/v1, apiKeyEnvar: WEC_API_KEY, temperature: 0 }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - id: openai:chat:Qwen3.5-122B</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    config: { apiBaseUrl: https://inference.wiline.com/v1, apiKeyEnvar: WEC_API_KEY, temperature: 0 }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">prompts:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - |</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    Clasifica este ticket de soporte. Devuelve SOLO JSON con claves:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    \"category\" (uno de: billing, technical, account, other),</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    \"priority\" (uno de: low, medium, high),</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    \"summary\" (cadena, máximo 12 palabras).</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    Ticket: {{ticket}}</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">defaultTest:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  options:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    transform: |</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      const m = output.match(/\\{[\\s\\S]*?\\}/);</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      return m ? m[0] : output;</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  assert:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    - type: is-json</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      value:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        type: object</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        required: [category, priority, summary]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        properties:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">          category: { type: string, enum: [billing, technical, account, other] }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">          priority: { type: string, enum: [low, medium, high] }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">          summary: { type: string }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">tests: file://tests.csv</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">EOF</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">npx </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-y</span><span class=\"token plain\"> promptfoo@latest </span><span class=\"token builtin class-name\">eval</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> promptfooconfig.yaml</span><br></div></code></pre></div></div>\n<p>El resultado es lo opuesto a lo que \"más grande es mejor\" predeciría:</p>\n<table><thead><tr><th>Modelo</th><th>Precisión</th><th>Tokens de finalización / ticket</th></tr></thead><tbody><tr><td><strong><code>Qwen2.5-3B-Instruct</code></strong> (no razonador)</td><td><strong>85%</strong> (34/40)</td><td>~30</td></tr><tr><td><code>Qwen3.5:9B</code> (razonador)</td><td>47.5% (19/40)</td><td>~965</td></tr><tr><td><code>Qwen3.5-122B</code> (razonador)</td><td>15% (6/40)</td><td>~1,013</td></tr></tbody></table>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Paso 5 — la matriz de modelos sobre el conjunto de datos de 40 filas\" src=\"https://wec.wiline.com/docs/es/assets/images/dataset-eval-matrix-9ffe20345f2040803fce117f6803a6ff.png\" width=\"2406\" height=\"1414\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 4.</strong> Mismo conjunto de datos, tres modelos — el pequeño modelo no razonador gana por mucho.</p>\n<p><strong>El pequeño modelo no razonador gana decisivamente — y cuanto más grande es el modelo razonador, <em>peor</em>\nlo hace.</strong> Los modelos razonadores no son más tontos al <em>clasificar</em>; <strong>enterraron el JSON en su\ncadena de pensamiento</strong>. <code>Qwen3.5:9B</code> filtra <code>Thinking: …</code> en la salida; <code>Qwen3.5-122B</code> a menudo devuelve\nuna <strong>respuesta vacía por completo</strong> — todos los ~1,000 tokens gastados razonando, nada parseable queda. También\nqueman <strong>~30× más tokens por ticket</strong>, razón por la cual la matriz completa tomó <strong>12m 31s</strong>, casi todo\nel tiempo esperando a los dos grandes modelos. <strong>Para salida estructurada, pensar en voz alta es una desventaja,\nno una ventaja</strong> — un hallazgo contraintuitivo que nunca obtendrías de una tabla de clasificación.</p>\n<p>Pero nota lo que realmente hizo posible ese hallazgo: <strong>el conjunto de datos.</strong> Con dos casos escritos a mano esos\nporcentajes serían lanzamientos de moneda; 40 filas etiquetadas son lo que convierte \"¿qué modelo?\" de una suposición en una\nmedición. La comparación es la <em>recompensa</em> por construir los datos — no un sustituto de ellos.</p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Habilidad desbloqueada 🏅</div><div class=\"admonitionContent_BuS1\"><p>Ahora puedes <strong>elegir un modelo con evidencia en lugar de sensaciones</strong> — algo que ninguna tabla de clasificación puede hacer por\nti, porque no conoce tu tarea.</p></div></div>\n<p>(El <code>transform</code> captura el primer bloque <code>{…}</code> con una coincidencia <strong>no codiciosa</strong> <code>*?</code> — un seguro barato para\nmodelos que envuelven JSON en prosa, exactamente como en <a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/\">parte 2</a>.)</p>\n<div class=\"theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z\"></path></svg></span>Evaluamos <code>category</code> aquí — <code>priority</code> es tu próxima afirmación</div><div class=\"admonitionContent_BuS1\"><p>La columna <code>__expected</code> califica solo la <strong>categoría</strong> predicha contra la etiqueta. El conjunto de datos también\nlleva una etiqueta de <code>priority</code> que aún no calificamos. Para calificar ambos campos a la vez, agrega una\nafirmación de <code>javascript</code> que analice la salida JSON y compare también <code>priority</code> — el mismo patrón exacto, una verificación más.\nCalificar más de lo que generaste es la forma más barata de hacer una evaluación más estricta.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-6--profundiza-en-las-fallas-del-ganador\">Paso 6 — Profundiza en las fallas del ganador<a href=\"https://wec.wiline.com/docs/es/tutorials/synthetic-eval-datasets-wiline-inference/#paso-6--profundiza-en-las-fallas-del-ganador\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 6 — Profundiza en las fallas del ganador\" title=\"Enlace directo al Paso 6 — Profundiza en las fallas del ganador\" translate=\"no\">​</a></h2>\n<p>Toma el ganador — <code>Qwen2.5-3B</code> con <strong>85% (34/40)</strong> — y extrae sus 6 tickets fallidos, comparando cada uno\ncon su etiqueta. (<code>__expected</code> no se almacena como una variable — Promptfoo lo consume como la afirmación —\nasí que lee la verdad de la fuente de datos). Debido a que los datos son recién generados, tus fallas exactas\ndiferirán, pero se dividen consistentemente en dos tipos reconocibles — y <strong>ninguno es solo \"el modelo es tonto\". Ejemplos representativos:</strong></p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El informe de Promptfoo — paso/falla por fila en el conjunto de datos de 40 casos\" src=\"https://wec.wiline.com/docs/es/assets/images/dataset-report-541b1a9921d846d5338c1b1547c49bd5.png\" width=\"2526\" height=\"1434\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 5.</strong> El informe web facilita escanear qué filas fallaron y abrir cada una.</p>\n<table><thead><tr><th>Ticket</th><th>Etiqueta</th><th>Por qué es complicado</th></tr></thead><tbody><tr><td>\"descuentos educativos para startups…\"</td><td><code>other</code></td><td><strong>Debatible</strong> — se trata de precios, así que <code>billing</code> es defensible</td></tr><tr><td>\"otorgar acceso IAM a S3 a un desarrollador junior…\"</td><td><code>account</code></td><td><strong>Debatible</strong> — control de acceso vs infraestructura</td></tr><tr><td>\"comentarios sobre la documentación de la API…\"</td><td><code>other</code></td><td><strong>Debatible</strong> — comentarios vs <code>technical</code></td></tr><tr><td>\"actualizar la tarjeta <strong>y</strong> agregar un usuario…\"</td><td><code>account</code></td><td><strong>Multi-intención</strong> — genuinamente dos categorías a la vez</td></tr></tbody></table>\n<p>El resto son malas clasificaciones ordinarias. Las lecciones reales, del día a día:</p>\n<ul>\n<li class=\"\"><strong>Tus etiquetas sintéticas no son verdad absoluta.</strong> Un bloque de \"fallas\" son disputas de etiquetas — revísalas y\ncorrígelas, o tu evaluación mide lo incorrecto.</li>\n<li class=\"\"><strong>La clasificación de una sola etiqueta falla en tickets de múltiples intenciones.</strong> Las entradas reales no siempre son de una categoría.</li>\n<li class=\"\"><strong>Un 85% de encabezado oculta ambas.</strong> Solo aprendes <em>por qué</em> leyendo las filas que fallaron — que es la\nrazón completa para evaluar en un conjunto de datos real en lugar de en dos ejemplos.</li>\n</ul>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"lo-que-construimos\">Lo que construimos<a href=\"https://wec.wiline.com/docs/es/tutorials/synthetic-eval-datasets-wiline-inference/#lo-que-construimos\" class=\"hash-link\" aria-label=\"Enlace directo al Lo que construimos\" title=\"Enlace directo al Lo que construimos\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\">La habilidad central: un pipeline repetible para <strong>generar, validar, deduplicar y versionar</strong> un conjunto de datos de evaluación\ndesde la API de WEC — datos de prueba reales en lugar de dos casos escritos a mano.</li>\n<li class=\"\">La disciplina que lo hace confiable: <strong>las etiquetas generadas no son verdad absoluta.</strong> Validas enums\ny equilibrio, y <em>revisas las disputas</em> — un bloque de \"fallas\" son tus propias etiquetas siendo incorrectas.</li>\n<li class=\"\">Prueba de que importa: <strong>la cobertura revela lo que dos casos ocultan</strong> — malas clasificaciones genuinas <em>y</em>\ntickets debatibles y de múltiples intenciones que nunca pensarías en escribir a mano.</li>\n<li class=\"\">Un bono que los datos desbloquean: una <strong>comparación de modelos confiable</strong> — aquí un <strong>modelo no razonador de 3B\nsuperó a uno razonador de 122B</strong> para salida estructurada, lo opuesto a \"más grande es mejor.\"</li>\n</ul>\n<p>Compromete <code>tickets_dataset.jsonl</code> y <code>tests.csv</code> junto a tu configuración de evaluación, y regenera/crece\nel conjunto de datos a medida que tu producto cambia.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"solución-de-problemas\">Solución de problemas<a href=\"https://wec.wiline.com/docs/es/tutorials/synthetic-eval-datasets-wiline-inference/#soluci%C3%B3n-de-problemas\" class=\"hash-link\" aria-label=\"Enlace directo al Solución de problemas\" title=\"Enlace directo al Solución de problemas\" translate=\"no\">​</a></h2>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"401--clave-de-api-inválida\"><a id=\"ts-401\"></a>401 / clave de API inválida<a href=\"https://wec.wiline.com/docs/es/tutorials/synthetic-eval-datasets-wiline-inference/#401--clave-de-api-inv%C3%A1lida\" class=\"hash-link\" aria-label=\"Enlace directo al 401--clave-de-api-inválida\" title=\"Enlace directo al 401--clave-de-api-inválida\" translate=\"no\">​</a></h3>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">{ \"error\": { \"message\": \"Error de autenticación, clave de API inválida\", \"code\": \"401\" } }</span><br></div></code></pre></div></div>\n<p>La clave ha expirado, ha sido revocada o está mal formada. Genera una nueva en <strong>Inferencia → Claves de API</strong>,\nreexporta y vuelve a sanear (<code>tr -cd '[:print:]'</code>) en caso de que el pegado haya llevado un carácter invisible.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"jq-no-se-puede-indexar-un-array-con-cadena\"><a id=\"ts-jq\"></a>jq: \"No se puede indexar un array con cadena\"<a href=\"https://wec.wiline.com/docs/es/tutorials/synthetic-eval-datasets-wiline-inference/#jq-no-se-puede-indexar-un-array-con-cadena\" class=\"hash-link\" aria-label=\"Enlace directo al jq-no-se-puede-indexar-un-array-con-cadena\" title=\"Enlace directo al jq-no-se-puede-indexar-un-array-con-cadena\" translate=\"no\">​</a></h3>\n<p>Esto proviene de <code>[\"billing\", ...] | index(.category)</code> — dentro del pipe, <code>.</code> es el array, así que\n<code>.category</code> indexa el array. Usa el idiom <code>IN()</code> en su lugar:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">jq </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'select((.category|IN(\"billing\",\"technical\",\"account\",\"other\")|not))'</span><span class=\"token plain\"> tickets.jsonl</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El error &amp;quot;No se puede indexar un array con cadena&amp;quot; de jq\" src=\"https://wec.wiline.com/docs/es/assets/images/dataset-ts-jq-index-ec85a2581780363d791db73ff2f03fd0.png\" width=\"2016\" height=\"1402\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 6.</strong> El error de <code>index(.field)</code> — dentro del pipe, jq intenta indexar el array con una cadena.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"tu-conjunto-de-datos-tiene-filas-que-son-solo-null\"><a id=\"ts-null\"></a>Tu conjunto de datos tiene filas que son solo <code>null</code><a href=\"https://wec.wiline.com/docs/es/tutorials/synthetic-eval-datasets-wiline-inference/#tu-conjunto-de-datos-tiene-filas-que-son-solo-null\" class=\"hash-link\" aria-label=\"Enlace directo al tu-conjunto-de-datos-tiene-filas-que-son-solo-null\" title=\"Enlace directo al tu-conjunto-de-datos-tiene-filas-que-son-solo-null\" translate=\"no\">​</a></h3>\n<p>Una fila <code>null</code> significa que una llamada de generación falló pero el bucle aún escribió su resultado (vacío). Causas habituales: una clave de API expirada/revocada, o el modelo que se agota. Soluciones: extraer con\n<code>.choices[0].message.content // empty</code> para que una llamada fallida escriba <strong>nada</strong> en lugar de <code>null</code>;\nvuelve a verificar <code>wc -l</code> después de cada ejecución de generación; y si las llamadas se cuelgan, agrega un tiempo de espera por solicitud\n(<code>curl -m 60</code>) y considera un modelo más ligero y rápido para la generación masiva.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"__expected-es-null-en-el-json-de-resultados\"><code>__expected</code> es <code>null</code> en el JSON de resultados<a href=\"https://wec.wiline.com/docs/es/tutorials/synthetic-eval-datasets-wiline-inference/#__expected-es-null-en-el-json-de-resultados\" class=\"hash-link\" aria-label=\"Enlace directo al __expected-es-null-en-el-json-de-resultados\" title=\"Enlace directo al __expected-es-null-en-el-json-de-resultados\" translate=\"no\">​</a></h3>\n<p>Eso es esperado — Promptfoo <strong>consume</strong> la columna <code>__expected</code> del CSV como la afirmación de la fila, así que\nno se mantiene como una variable. Para inspeccionar las etiquetas de verdad para las filas que fallan, léelas de tu\nconjunto de datos fuente (<code>tickets_dataset.jsonl</code>) en lugar del archivo de resultados.</p>\n<div class=\"skillComplete\"><span class=\"skillComplete__medal\" aria-hidden=\"true\">🎯</span><div class=\"skillComplete__text\"><div class=\"skillComplete__title\">Finished this tutorial?</div><div class=\"skillComplete__sub\">Mark it complete to earn <strong>Real test data at scale</strong> on your skill path.</div></div><button type=\"button\" class=\"skillComplete__btn\">Mark as complete ✓</button></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"qué-sigue\">¿Qué sigue?<a href=\"https://wec.wiline.com/docs/es/tutorials/synthetic-eval-datasets-wiline-inference/#qu%C3%A9-sigue\" class=\"hash-link\" aria-label=\"Enlace directo al ¿Qué sigue?\" title=\"Enlace directo al ¿Qué sigue?\" translate=\"no\">​</a></h2>\n<p>Ahora tienes un conjunto de datos real — pero se ejecuta en CI, fuera de línea. El siguiente paso es observar tu aplicación en\n<strong>producción</strong>: autoalojar <strong>Langfuse</strong> para rastrear cada llamada, seguir la latencia y el costo, y ejecutar\nevaluaciones (usando <em>este conjunto de datos</em>) contra el tráfico en vivo. Ahí es donde las pruebas fuera de línea se convierten en\nuna verdadera observabilidad — y es la próxima publicación de la serie.</p>",
            "url": "https://wec.wiline.com/docs/es/tutorials/synthetic-eval-datasets-wiline-inference/",
            "title": "Deja de escribir casos de prueba a mano: genera un conjunto de evaluación con la API de WEC",
            "summary": "Dos casos de prueba escritos a mano no son una evaluación. Usa la API de Inferencia de WEC para generar un conjunto de datos de evaluación etiquetado — luego valídalo y curátalo, porque las etiquetas generadas no son automáticamente correctas. El resultado es datos de prueba reales a gran escala; aliméntalo a tu arnés y la cobertura revela las malas clasificaciones y las etiquetas debatibles que dos casos ocultarían. Cada comando y resultado es real.",
            "date_modified": "2026-07-06T00:00:00.000Z",
            "author": {
                "name": "Rafael Fernandes",
                "url": "https://www.linkedin.com/in/rafaelmacariofernandes/"
            },
            "tags": [
                "ai",
                "evals",
                "promptfoo",
                "inference",
                "datasets",
                "synthetic-data",
                "observability"
            ]
        },
        {
            "id": "https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/",
            "content_html": "<div class=\"tutorialHero\"><img class=\"tutorialHero__brand\" src=\"https://wec.wiline.com/docs/es/assets/images/promptfoo-avatar-31b32c657d72dac0a7edbd77c24ec717.jpg\" alt=\"Promptfoo\"><span class=\"tutorialHero__plus\">+</span><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"242\" height=\"255\" fill=\"none\" viewBox=\"0 0 242 255\" class=\"tutorialHero__wiline\"><path fill=\"#4E8EF7\" d=\"M56.711 11.293A195 195 0 0 1 41.899 4.79c4.788 1.85 9.71 4.028 14.812 6.504M73.807 29.35c-1.302-1.446-2.666-3.233-4.02-5.38 1.326 1.44 2.642 3.24 4.02 5.38M69.761 23.354c-.832-.585-1.752-1.472-2.558-2.714.9.567 1.685 1.49 2.558 2.714M62.97 15.774a10.7 10.7 0 0 1-3.158-1.841c.993.299 1.986.956 3.159 1.841M59.715 13.346c-.818-.194-1.782-.674-2.829-1.494.84.175 1.761.691 2.83 1.494M39.317 3.782c-.524.192-1.413.302-2.455.14.594-.255 1.342-.238 2.455-.14M74.906 31.339c-.334-.153-.73-.61-1.117-1.398.358.143.706.619 1.117 1.398M66.024 18.878c-.239.1-.557.05-.752-.276.35-.201.532-.069.752.276M71.773 150.625c3.363 7.291 7.185 14.411 9.983 21.914 4.474 11.997 11.167 20.832 24.296 24.757 10.62 3.175 20.369 1.795 30.093-1.816 9.937-3.689 15.165-11.978 18.501-21.541 4.349-12.466 9.385-24.443 19.668-33.446 11.32-9.909 24.639-14.788 39.429-15.63 8.3-.473 16.649-.081 25.444.444.472 15.673.385 30.825.55 45.974.038 3.499-1.049 4.719-4.557 4.543-4.982-.25-9.987-.079-14.98.009-.209.004-.4.991-.6 1.521-23.106 1.695-37.691 15.22-47.961 34.651-3.387 6.408-5.913 13.38-10.013 19.273-4.368 6.277-10.134 11.582-15.853 17.196-6.439 1.421-12.244 3.342-18.202 4.416-12.627 2.274-24.661.457-35.764-6.356-1.028-.631-2.395-.709-3.604-1.043 0 0-.117-.11-.047-.495-.818-.973-1.706-1.562-2.593-2.15 0 0 .09-.044.05-.335-.513-.545-.988-.798-1.463-1.052 0 0-.07-.123.012-.492-.561-.894-1.205-1.421-1.848-1.947 0 0 .134-.068.102-.28-.321-.442-.61-.671-.898-.901 0 0 .086-.007.074-.336-2.05-3.429-4.089-6.529-6.127-9.629 0 0 .094-.017.123-.365-3.013-6.117-6.056-11.886-9.1-17.655 0 0 .096-.01.088-.342-1.978-4.073-3.89-7.848-5.997-11.51-.246-.428-1.572-.235-2.398-.329 0 0-.226-.202-.114-.613-.213-.499-.537-.587-.861-.674 0 0-.032-.101.02-.455-.62-.846-1.291-1.339-1.964-1.832 0 0 .133-.083.119-.372-2.476-4.624-5.645-8.048-10.914-8.777 0 0 .108-.031.01-.326-1.673-1.444-3.105-2.918-4.85-3.688-9.751-4.308-20.013-5.643-30.63-5.23-7.305.285-7.316.051-7.317-7.446q-.002-18.496-.002-36.992v-3.57h36.106s-.08.019.133.283c1.43.415 2.646.567 3.863.719 0 0-.077.022.113.296 1.804.591 3.417.908 5.03 1.225 0 0 .139-.037.152.369 1.592.962 3.17 1.517 4.748 2.073 0 0-.091.027.052.267.735.415 1.328.59 1.92.765 0 0-.076.033.053.246.477.325.825.438 1.173.551 0 0-.149.018-.07.246.418.239.757.251 1.096.263 0 0 .23.058.189.465 1.522 1.319 3.084 2.231 4.647 3.143 0 0-.105.033-.015.316 2.723 2.764 5.356 5.244 7.989 7.724 0 0-.104.062-.094.352 1.01 1.401 2.01 2.512 3.01 3.624\"></path><path fill=\"#4E8EF7\" d=\"M214.77 52.819c-3.343.846-6.723 1.574-10.024 2.562-11.388 3.408-20.567 10.142-27.166 19.865-5.097 7.511-9.621 15.483-13.685 23.608-6.809 13.613-15.725 24.809-31.343 28.198-24.707 5.362-45.22-.126-57.852-24.564C69.466 92.363 63.8 82.375 57.349 73c-3.369-4.896-8.866-8.326-13.679-13.041-1.11-1.409-1.81-2.472-2.794-2.937-10.697-5.056-21.87-7.3-33.686-5.344-3.877.641-5.72-.297-5.615-4.86.294-12.816.26-25.646.019-38.464C1.509 3.874 3.15 2.5 7.429 2.716c9.774.492 19.564.678 29.347.989 0 0-.076.026.082.257 1.046.122 1.935.012 2.825-.098.575.043 1.15.086 2.038.692 5.265 2.72 10.218 4.877 15.17 7.034 0 0-.108.03-.016.317 1.058.765 2.022 1.245 2.987 1.724 0 0-.113.048-.082.354 1.144.876 2.258 1.446 3.372 2.017 0 0 .18.145.097.506.223.404.53.447.837.49 0 0 .214.13.144.504.244.426.558.477.873.528 0 0 .171.124.108.485.256.412.574.464.893.515 0 0 .167.127.105.493.265.437.592.509.919.58 0 0 .078.108.019.5.861 1.28 1.782 2.166 2.702 3.053 0 0-.101.061-.087.368 1.379 2.093 2.743 3.88 4.107 5.666 0 0-.094.011-.092.297.4.742.795 1.2 1.19 1.656 0 0-.121.02-.165.337.685 1.432 1.413 2.549 2.14 3.665 0 0-.11.024-.129.299.326.776.67 1.277 1.014 1.78 0 0-.084-.005-.1.367.933 4.079.311 8.497 4.648 10.67 4.69 12.164 12.107 21.794 25.852 23.497 19.618 2.431 35.321.062 45.299-24.554 3.398-8.383 7.099-16.902 12.232-24.264 8.353-11.979 21.046-17.795 35.832-20.32 1.666.417 2.649.651 3.624.622 9.93-.298 19.865-.513 29.785-1.032 3.818-.2 4.825 1.198 4.746 4.853-.218 9.967.004 19.944-.109 29.915-.052 4.583 1.149 10.606-1.239 13.302-2.044 2.308-8.394.86-12.842.967-3.82.092-7.644.036-11.467.045.065.31.097.633.213.922.034.084.306.072.469.105M37.585 127.354c-11.585.344-23.368.344-35.907.344v3.57q0 18.496.002 36.992c0 7.497.012 7.731 7.318 7.446 10.616-.413 20.878.922 30.63 5.23 1.743.77 3.176 2.244 4.84 3.744-13.655-7.873-28.93-7.767-44.468-7.398v-52.503c12.516.308 25.063-1.222 37.585 2.575\"></path><path fill=\"#4E8EF7\" d=\"M36.619 3.401c-9.626-.007-19.415-.193-29.19-.685C3.151 2.5 1.51 3.874 1.594 8.354c.241 12.818.275 25.648-.018 38.464-.105 4.563 1.737 5.501 5.614 4.86 11.816-1.956 22.99.288 33.686 5.344.984.465 1.685 1.529 2.616 2.66-13.39-7.538-28.284-7.343-43.479-6.78V0C12.314 1.045 24.388 2.071 36.62 3.401M215.232 52.852c-.625-.066-.897-.054-.931-.138-.116-.29-.148-.612-.212-.922 3.822-.01 7.646.047 11.467-.045 4.448-.108 10.797 1.341 12.842-.967 2.387-2.696 1.186-8.719 1.238-13.302.114-9.971-.109-19.948.109-29.915.08-3.655-.928-5.052-4.746-4.853-9.919.519-19.855.734-29.785 1.032-.975.03-1.958-.205-3.228-.58C214.559 1.945 227.423.991 240.73.003v52.882c-8.276 0-16.656 0-25.498-.033M220.071 177.398c-.271-.573-.08-1.561.129-1.565 4.993-.088 9.998-.259 14.98-.009 3.508.176 4.595-1.044 4.557-4.543-.165-15.149-.078-30.301-.088-45.911.458.372 1.32 1.202 1.324 2.036.079 16.424.059 32.848.059 50.036-6.984 0-13.737 0-20.961-.044M88.39 245.741c1.022.084 2.39.162 3.417.793 11.102 6.813 23.137 8.63 35.763 6.356 5.959-1.073 11.764-2.995 17.871-4.34-5.612 2.132-11.343 5.166-17.325 5.79-13.898 1.451-27.803 1.213-39.725-8.599M66.556 210.212c2.975 5.411 6.018 11.18 9.02 17.344-3.011-5.4-5.982-11.193-9.02-17.344M58.341 197.934c.666-.167 1.992-.36 2.238.068 2.107 3.662 4.018 7.437 5.985 11.557-2.69-3.535-5.376-7.449-8.223-11.625M44.637 185.241c5.099.438 8.268 3.862 10.737 8.564-3.518-2.513-7.043-5.393-10.737-8.564M82.29 48.422c-4.252-1.834-3.63-6.252-4.548-10.37 1.508 3.069 2.986 6.55 4.548 10.37M68.757 146.326c-2.533-2.157-5.166-4.637-7.882-7.455 2.539 2.151 5.16 4.641 7.882 7.455M75.532 228.226a122 122 0 0 1 6.047 9.319c-1.993-2.742-3.986-5.854-6.047-9.319M71.695 150.312c-.922-.799-1.921-1.91-2.906-3.366.952.788 1.89 1.921 2.906 3.366M46.663 130.007c-1.353-.103-2.966-.42-4.767-1.051 1.377.07 2.941.454 4.767 1.051M51.647 132.369a13.1 13.1 0 0 1-4.501-1.77c1.48.227 2.902.852 4.5 1.77M60.724 138.321a17.7 17.7 0 0 1-4.395-2.781c1.501.609 2.869 1.551 4.395 2.781M76.889 35.31c-.674-.781-1.402-1.898-2.068-3.39.711.768 1.362 1.912 2.068 3.39M85.668 243.124c.782.31 1.67.899 2.423 1.825-.862-.29-1.59-.919-2.423-1.825M41.596 128.372c-1.033.176-2.25.024-3.68-.432 1.023-.169 2.26-.032 3.68.432M55.367 194.37c.568.222 1.24.715 1.808 1.548-.637-.199-1.17-.738-1.808-1.548M82.397 239.295c.56.251 1.204.778 1.71 1.628-.633-.235-1.13-.794-1.71-1.628M84.291 241.679c.334.034.809.287 1.298.875-.376.004-.766-.326-1.298-.875M53.64 133.426c-.438.094-1.03-.081-1.764-.543.441-.101 1.026.086 1.764.543M77.805 37.409c-.322-.188-.666-.69-.978-1.52.34.182.648.693.978 1.52M81.588 238.108c.218-.039.507.19.823.707-.233.045-.493-.196-.823-.707M55.906 134.832c-.198.157-.537.145-.92-.161.262-.29.537-.195.92.161M57.276 196.552c.254-.079.578.009.756.361-.377.171-.557.018-.756-.361M54.888 134.25c-.215.129-.563.017-1.038-.351.217-.133.56-.012 1.038.351M64.014 16.858a.545.545 0 0 1-.71-.247c.344-.19.51-.067.71.247M65.027 17.882a.574.574 0 0 1-.734-.272c.35-.193.525-.063.734.272M67.045 19.939c-.244.093-.57.021-.777-.329.358-.192.55-.044.777.329\"></path></svg><span class=\"tutorialHero__plus\">+</span><img class=\"tutorialHero__brand\" src=\"data:image/png;base64,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\" alt=\"JSON\"></div>\n<div class=\"skillTracker\" tabindex=\"0\" aria-label=\"Skill path: 0 of 8 skills earned\"><div class=\"skillTracker__ring\" style=\"--skillTracker-pct:0%\"><span class=\"skillTracker__ringLabel\">0<!-- -->/<!-- -->8</span></div><div class=\"skillTracker__panel\"><div class=\"skillTracker__header\"><span class=\"skillTracker__title\">🎯 Skill path</span><span class=\"skillTracker__count\">0<!-- -->/<!-- -->8<!-- --> earned</span></div><div class=\"skillTracker__series\">AI evals &amp; observability</div><ul class=\"skillTracker__steps\"><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">1</span><span class=\"skillTracker__skill\" data-state=\"locked\">Prove a model works</span></a></li><li><span class=\"skillTracker__step\"><span class=\"skillTracker__dot\" data-state=\"current\">2</span><span class=\"skillTracker__skill\" data-state=\"current\">Trustworthy JSON</span></span></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/synthetic-eval-datasets-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">3</span><span class=\"skillTracker__skill\" data-state=\"locked\">Real test data at scale</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/\"><span class=\"skillTracker__dot\" data-state=\"locked\">4</span><span class=\"skillTracker__skill\" data-state=\"locked\">Observe &amp; score production</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">5</span><span class=\"skillTracker__skill\" data-state=\"locked\">RAG, end to end</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/deepeval-regression-testing-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">6</span><span class=\"skillTracker__skill\" data-state=\"locked\">Catch regressions in CI</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/component-tracing-agent-tool-calls-wiline/\"><span class=\"skillTracker__dot\" data-state=\"locked\">7</span><span class=\"skillTracker__skill\" data-state=\"locked\">Trace &amp; debug agent tool calls</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/web-search-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">🏆</span><span class=\"skillTracker__skill\" data-state=\"locked\">Add live web search</span></a></li></ul><div class=\"skillTracker__footer\">Finish this tutorial, then mark it complete below to earn the skill.</div></div></div>\n<p>\"Devuelve solo JSON\" es una de las instrucciones más comunes en aplicaciones LLM en producción — y una de las menos fiables. Los modelos envuelven JSON en cercas de markdown, añaden una frase amigable, o (si son modelos de razonamiento) narran todo su proceso de pensamiento alrededor de ello. Cualquiera de esos casos rompe un estricto <code>JSON.parse</code>, y tu pipeline se cae.</p>\n<p>En esta guía construimos una evaluación de <a href=\"https://www.promptfoo.dev/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Promptfoo</a> que hace que los modelos de <strong>API de Inferencia WEC</strong> clasifiquen tickets de soporte en <strong>JSON validado por esquema</strong>, luego lo endurecemos contra el desorden del mundo real — y lo usamos para elegir un modelo en el que realmente puedes confiar. Esta es la parte 2 de la serie de evaluaciones (consulta <a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/\">parte 1</a> para la configuración inicial). Todo aquí se ejecutó en vivo contra <code>https://inference.wiline.com</code>.</p>\n<!-- -->\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"cómo-funciona-la-evaluación\">Cómo funciona la evaluación<a href=\"https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/#c%C3%B3mo-funciona-la-evaluaci%C3%B3n\" class=\"hash-link\" aria-label=\"Enlace directo al Cómo funciona la evaluación\" title=\"Enlace directo al Cómo funciona la evaluación\" translate=\"no\">​</a></h2>\n<p>Cada prueba pasa por el mismo pipeline. El detalle que más importa: el <strong><code>transform</code> se ejecuta en la salida del modelo <em>antes</em> de que la afirmación la vea</strong> — eso es lo que nos permite recuperar JSON desordenado antes de validarlo.</p>\n<!-- -->\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"requisitos-previos\">Requisitos previos<a href=\"https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/#requisitos-previos\" class=\"hash-link\" aria-label=\"Enlace directo al Requisitos previos\" title=\"Enlace directo al Requisitos previos\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\">\n<p>Una <strong>clave de API de Inferencia WEC</strong> — consulta <a class=\"\" href=\"https://wec.wiline.com/docs/es/cloud_portal/platform/inference/api_keys/\">Inferencia → Claves de API</a>.</p>\n</li>\n<li class=\"\">\n<p>Los nombres de los modelos utilizados aquí (<code>GLM-5.2</code>, <code>gemma4</code>, <code>Qwen2.5-3B-Instruct</code>, <code>qwen3.5:9B</code>) provienen del <a class=\"\" href=\"https://wec.wiline.com/docs/es/cloud_portal/platform/inference/models_hub/\">Hub de Modelos</a> — intercambia por lo que hayas habilitado.</p>\n</li>\n<li class=\"\">\n<p><strong>Node.js 22+</strong> y tu clave exportada (Promptfoo se ejecuta a través de <code>npx</code>):</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">nvm use </span><span class=\"token number\" style=\"color:#36acaa\">24</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token builtin class-name\">export</span><span class=\"token plain\"> </span><span class=\"token assign-left variable\" style=\"color:#36acaa\">WEC_API_KEY</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token string\" style=\"color:#e3116c\">'sk-tu-clave'</span><br></div></code></pre></div></div>\n</li>\n</ul>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>tip</div><div class=\"admonitionContent_BuS1\"><p>Si tu primera ejecución da error con <code>Cannot convert argument to a ByteString</code>, tu clave pegada tiene un carácter invisible — consulta <a href=\"https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/#ts-invisible-key\" class=\"\">Solución de problemas</a> al final.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-1--un-esquema-estricto-y-un-fallo-real\">Paso 1 — Un esquema estricto y un fallo real<a href=\"https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/#paso-1--un-esquema-estricto-y-un-fallo-real\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 1 — Un esquema estricto y un fallo real\" title=\"Enlace directo al Paso 1 — Un esquema estricto y un fallo real\" translate=\"no\">​</a></h2>\n<p>Haremos algo más cercano a una carga de trabajo real que \"capital de Francia\": clasificar un ticket de soporte en <code>categoría</code>, <code>prioridad</code> y un breve <code>resumen</code>. La mejora clave sobre una verificación básica es el <strong>Esquema JSON</strong> — impone <code>enum</code>s, por lo que un modelo que inventa una categoría falla.</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">cat</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> promptfooconfig.yaml </span><span class=\"token operator\" style=\"color:#393A34\">&lt;&lt;</span><span class=\"token string\" style=\"color:#e3116c\">'EOF'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">description: \"API de Inferencia WEC — salida estructurada (esquema JSON)\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">providers:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - id: openai:chat:zai-org/GLM-5.2</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    config:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      apiBaseUrl: https://inference.wiline.com/v1</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      apiKeyEnvar: WEC_API_KEY</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      temperature: 0</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">prompts:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - |</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    Clasifica este ticket de soporte. Devuelve SOLO JSON con claves:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    \"categoría\" (una de: facturación, técnica, cuenta, otra),</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    \"prioridad\" (una de: baja, media, alta),</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    \"resumen\" (cadena, máximo 12 palabras).</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    Ticket: {{ticket}}</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">defaultTest:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  assert:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    - type: is-json</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      value:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        type: object</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        required: [categoría, prioridad, resumen]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        properties:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">          categoría: { type: string, enum: [facturación, técnica, cuenta, otra] }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">          prioridad: { type: string, enum: [baja, media, alta] }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">          resumen: { type: string }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">tests:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - vars: { ticket: \"Me cobraron dos veces por mi instancia este mes.\" }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    assert: [{ type: icontains, value: facturación }]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - vars: { ticket: \"Mi VM no arranca después de la última restauración de instantánea.\" }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    assert: [{ type: icontains, value: técnica }]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">EOF</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">npx </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-y</span><span class=\"token plain\"> promptfoo@latest </span><span class=\"token builtin class-name\">eval</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> promptfooconfig.yaml</span><br></div></code></pre></div></div>\n<p>El resultado — <strong>50%</strong>, y el fallo es instructivo:</p>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">ticket                                    [zai-org/GLM-5.2]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Me cobraron dos veces por mi instancia…      [FALLIDO] ```json</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">                                          { \"categoría\": \"facturación\", \"prioridad\": \"alta\", … }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">                                          ```</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Mi VM no arranca después de la última…        [APROBADO] { \"categoría\": \"técnica\", … }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">✓ 1 aprobado (50%)   ✗ 1 fallido (50%)</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Paso 1 — un ticket falla porque el JSON está envuelto en cercas de markdown\" src=\"https://wec.wiline.com/docs/es/assets/images/json-step1-schema-fail-dc2b9c12c0ee29b0086499d5c76f2fd6.png\" width=\"964\" height=\"756\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 1.</strong> Mismo modelo, mismo aviso, temperatura 0 — una respuesta es limpia, la otra está envuelta en <code>```json</code> cercas, por lo que <code>is-json</code> no puede analizarla.</p>\n<p>El <em>contenido</em> es correcto; el <em>formato</em> no lo es. <code>is-json</code> valida tanto \"¿es JSON analizable?\" como \"¿cumple con el esquema?\" — y la salida cercada falla la primera prueba.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-2--recuperar-salida-cercada-con-un-transform\">Paso 2 — Recuperar salida cercada con un <code>transform</code><a href=\"https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/#paso-2--recuperar-salida-cercada-con-un-transform\" class=\"hash-link\" aria-label=\"Enlace directo al paso-2--recuperar-salida-cercada-con-un-transform\" title=\"Enlace directo al paso-2--recuperar-salida-cercada-con-un-transform\" translate=\"no\">​</a></h2>\n<p>Un <code>transform</code> se ejecuta en la salida del modelo <strong>antes</strong> de las afirmaciones. Aquí eliminamos las cercas.\n(Este bloque utiliza cuatro comillas porque la configuración en sí contiene tres comillas).</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">cat</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> promptfooconfig.yaml </span><span class=\"token operator\" style=\"color:#393A34\">&lt;&lt;</span><span class=\"token string\" style=\"color:#e3116c\">'EOF'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">description: \"API de Inferencia WEC — transformación de eliminación de cercas\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">providers:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - id: openai:chat:zai-org/GLM-5.2</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    config:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      apiBaseUrl: https://inference.wiline.com/v1</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      apiKeyEnvar: WEC_API_KEY</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      temperature: 0</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">prompts:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - |</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    Clasifica este ticket de soporte. Devuelve SOLO JSON con claves:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    \"categoría\" (una de: facturación, técnica, cuenta, otra),</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    \"prioridad\" (una de: baja, media, alta),</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    \"resumen\" (cadena, máximo 12 palabras).</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    Ticket: {{ticket}}</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">defaultTest:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  options:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    transform: |</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      return output.replace(/```json\\n?|\\n?```/g, '').trim();</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  assert:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    - type: is-json</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      value:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        type: object</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        required: [categoría, prioridad, resumen]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        properties:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">          categoría: { type: string, enum: [facturación, técnica, cuenta, otra] }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">          prioridad: { type: string, enum: [baja, media, alta] }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">          resumen: { type: string }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">tests:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - vars: { ticket: \"Me cobraron dos veces por mi instancia este mes.\" }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    assert: [{ type: icontains, value: facturación }]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - vars: { ticket: \"Mi VM no arranca después de la última restauración de instantánea.\" }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    assert: [{ type: icontains, value: técnica }]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">EOF</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">npx </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-y</span><span class=\"token plain\"> promptfoo@latest </span><span class=\"token builtin class-name\">eval</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> promptfooconfig.yaml --no-cache</span><br></div></code></pre></div></div>\n<p>Ahora ambos pasan — <strong>2/2</strong>. Las cercas se eliminan antes de que <code>is-json</code> vea la salida.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Paso 2 — con la transformación de eliminación de cercas, ambos tickets pasan\" src=\"https://wec.wiline.com/docs/es/assets/images/json-step2-transform-pass-14cb388c6725d0aacffeee1bb320f553.png\" width=\"966\" height=\"735\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 2.</strong> El <code>transform</code> elimina las cercas de código, por lo que el ticket que antes fallaba se analiza.</p>\n<div class=\"theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z\"></path></svg></span><code>transform</code> necesita un <code>return</code> explícito</div><div class=\"admonitionContent_BuS1\"><p>Un <code>transform</code> de varias líneas es un cuerpo de función, no una expresión — omite <code>return</code> y obtienes <code>Transform function did not return a value</code>. Consulta <a href=\"https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/#ts-transform-return\" class=\"\">Solución de problemas</a>.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-3--la-solución-más-limpia-modo-json\">Paso 3 — La solución más limpia: modo JSON<a href=\"https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/#paso-3--la-soluci%C3%B3n-m%C3%A1s-limpia-modo-json\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 3 — La solución más limpia: modo JSON\" title=\"Enlace directo al Paso 3 — La solución más limpia: modo JSON\" translate=\"no\">​</a></h2>\n<p>En lugar de limpiar después del modelo, pide a la API que emita solo JSON válido a través de <code>response_format</code>. Sin <code>transform</code> esta vez:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">cat</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> promptfooconfig.yaml </span><span class=\"token operator\" style=\"color:#393A34\">&lt;&lt;</span><span class=\"token string\" style=\"color:#e3116c\">'EOF'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">description: \"API de Inferencia WEC — modo JSON (response_format)\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">providers:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - id: openai:chat:zai-org/GLM-5.2</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    config:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      apiBaseUrl: https://inference.wiline.com/v1</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      apiKeyEnvar: WEC_API_KEY</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      temperature: 0</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      response_format: { type: json_object }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">prompts:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - |</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    Clasifica este ticket de soporte. Devuelve SOLO JSON con claves:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    \"categoría\" (una de: facturación, técnica, cuenta, otra),</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    \"prioridad\" (una de: baja, media, alta),</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    \"resumen\" (cadena, máximo 12 palabras).</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    Ticket: {{ticket}}</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">defaultTest:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  assert:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    - type: is-json</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      value:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        type: object</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        required: [categoría, prioridad, resumen]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        properties:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">          categoría: { type: string, enum: [facturación, técnica, cuenta, otra] }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">          prioridad: { type: string, enum: [baja, media, alta] }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">          resumen: { type: string }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">tests:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - vars: { ticket: \"Me cobraron dos veces por mi instancia este mes.\" }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    assert: [{ type: icontains, value: facturación }]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - vars: { ticket: \"Mi VM no arranca después de la última restauración de instantánea.\" }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    assert: [{ type: icontains, value: técnica }]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">EOF</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">npx </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-y</span><span class=\"token plain\"> promptfoo@latest </span><span class=\"token builtin class-name\">eval</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> promptfooconfig.yaml --no-cache</span><br></div></code></pre></div></div>\n<p><strong>2/2 APROBADO</strong> sin transformación — parece que el modo JSON funciona.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Paso 3 — el modo JSON pasa en GLM-5.2\" src=\"https://wec.wiline.com/docs/es/assets/images/json-step3-jsonmode-6cabcc01d214aaa3b14b1a0842eeb720.png\" width=\"962\" height=\"718\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 3.</strong> Con <code>response_format: json_object</code>, GLM-5.2 devuelve JSON limpio, sin necesidad de transformación.</p>\n<p>Pero antes de confiar en <code>response_format</code>, necesitamos probarlo en todos los modelos — porque un solo resultado verde puede ser suerte, no cumplimiento.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-4--matriz-de-fiabilidad-entre-modelos-wec\">Paso 4 — Matriz de fiabilidad entre modelos WEC<a href=\"https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/#paso-4--matriz-de-fiabilidad-entre-modelos-wec\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 4 — Matriz de fiabilidad entre modelos WEC\" title=\"Enlace directo al Paso 4 — Matriz de fiabilidad entre modelos WEC\" translate=\"no\">​</a></h2>\n<p>Ejecuta la configuración en modo JSON contra los cuatro modelos de chat. Este es el beneficio — una <strong>comparación de fiabilidad específica de WEC</strong> que no puedes obtener de una tabla de clasificación genérica.</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">cat</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> promptfooconfig.yaml </span><span class=\"token operator\" style=\"color:#393A34\">&lt;&lt;</span><span class=\"token string\" style=\"color:#e3116c\">'EOF'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">description: \"API de Inferencia WEC — fiabilidad en modo JSON entre modelos\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">providers:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - id: openai:chat:zai-org/GLM-5.2</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    config: { apiBaseUrl: https://inference.wiline.com/v1, apiKeyEnvar: WEC_API_KEY, temperature: 0, response_format: { type: json_object } }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - id: openai:chat:gemma4</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    config: { apiBaseUrl: https://inference.wiline.com/v1, apiKeyEnvar: WEC_API_KEY, temperature: 0, response_format: { type: json_object } }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - id: openai:chat:Qwen2.5-3B-Instruct</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    config: { apiBaseUrl: https://inference.wiline.com/v1, apiKeyEnvar: WEC_API_KEY, temperature: 0, response_format: { type: json_object } }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - id: openai:chat:qwen3.5:9B</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    config: { apiBaseUrl: https://inference.wiline.com/v1, apiKeyEnvar: WEC_API_KEY, temperature: 0, response_format: { type: json_object } }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">prompts:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - |</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    Clasifica este ticket de soporte. Devuelve SOLO JSON con claves:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    \"categoría\" (una de: facturación, técnica, cuenta, otra),</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    \"prioridad\" (una de: baja, media, alta),</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    \"resumen\" (cadena, máximo 12 palabras).</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    Ticket: {{ticket}}</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">defaultTest:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  assert:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    - type: is-json</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      value:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        type: object</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        required: [categoría, prioridad, resumen]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        properties:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">          categoría: { type: string, enum: [facturación, técnica, cuenta, otra] }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">          prioridad: { type: string, enum: [baja, media, alta] }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">          resumen: { type: string }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">tests:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - vars: { ticket: \"Me cobraron dos veces por mi instancia este mes.\" }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    assert: [{ type: icontains, value: facturación }]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - vars: { ticket: \"Mi VM no arranca después de la última restauración de instantánea.\" }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    assert: [{ type: icontains, value: técnica }]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">EOF</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">npx </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-y</span><span class=\"token plain\"> promptfoo@latest </span><span class=\"token builtin class-name\">eval</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> promptfooconfig.yaml --no-cache</span><br></div></code></pre></div></div>\n<p><strong>4/8.</strong> GLM-5.2 y Qwen2.5-3B pasan limpiamente; <strong>gemma4 y qwen3.5:9B vierten su razonamiento</strong>\n(\"Proceso de Pensamiento…\") en lugar de JSON — a pesar de que <code>response_format</code> está configurado.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Paso 4 — con modo JSON, dos modelos pasan y dos emiten razonamiento\" src=\"https://wec.wiline.com/docs/es/assets/images/json-matrix-responseformat-d227b3a1ca8bffbf85f89ca2da11eb87.png\" width=\"826\" height=\"983\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 4.</strong> <code>response_format</code> configurado en los cuatro — pero los dos modelos orientados al razonamiento lo ignoran.</p>\n<table><thead><tr><th>Modelo</th><th>Resultado</th><th>Tokens (2 llamadas)</th></tr></thead><tbody><tr><td><code>zai-org/GLM-5.2</code></td><td>✅✅</td><td>202</td></tr><tr><td><code>Qwen2.5-3B-Instruct</code></td><td>✅✅</td><td>243</td></tr><tr><td><code>gemma4</code></td><td>❌❌ (razonamiento)</td><td>251</td></tr><tr><td><code>qwen3.5:9B</code></td><td>❌❌ (razonamiento)</td><td>1,757</td></tr></tbody></table>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Habilidad desbloqueada 🏅</div><div class=\"admonitionContent_BuS1\"><p>Ahora puedes <strong>medir qué modelos realmente honran el modo JSON</strong> — en lugar de confiar en la bandera.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-5--verificar-está-response_format-haciendo-algo\">Paso 5 — Verificar: ¿está <code>response_format</code> haciendo algo?<a href=\"https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/#paso-5--verificar-est%C3%A1-response_format-haciendo-algo\" class=\"hash-link\" aria-label=\"Enlace directo al paso-5--verificar-está-response_format-haciendo-algo\" title=\"Enlace directo al paso-5--verificar-está-response_format-haciendo-algo\" translate=\"no\">​</a></h2>\n<p>No asumas que el razonamiento fue <em>causado</em> por el modo JSON. Vuelve a ejecutar la misma matriz <strong>sin</strong>\n<code>response_format</code> (usando la transformación de eliminación de cercas en su lugar) y compara:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">cat</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> promptfooconfig.yaml </span><span class=\"token operator\" style=\"color:#393A34\">&lt;&lt;</span><span class=\"token string\" style=\"color:#e3116c\">'EOF'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">description: \"API de Inferencia WEC — matriz sin response_format\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">providers:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - id: openai:chat:zai-org/GLM-5.2</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    config: { apiBaseUrl: https://inference.wiline.com/v1, apiKeyEnvar: WEC_API_KEY, temperature: 0 }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - id: openai:chat:gemma4</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    config: { apiBaseUrl: https://inference.wiline.com/v1, apiKeyEnvar: WEC_API_KEY, temperature: 0 }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - id: openai:chat:Qwen2.5-3B-Instruct</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    config: { apiBaseUrl: https://inference.wiline.com/v1, apiKeyEnvar: WEC_API_KEY, temperature: 0 }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - id: openai:chat:qwen3.5:9B</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    config: { apiBaseUrl: https://inference.wiline.com/v1, apiKeyEnvar: WEC_API_KEY, temperature: 0 }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">prompts:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - |</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    Clasifica este ticket de soporte. Devuelve SOLO JSON con claves:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    \"categoría\" (una de: facturación, técnica, cuenta, otra),</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    \"prioridad\" (una de: baja, media, alta),</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    \"resumen\" (cadena, máximo 12 palabras).</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    Ticket: {{ticket}}</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">defaultTest:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  options:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    transform: |</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      return output.replace(/```json\\n?|\\n?```/g, '').trim();</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  assert:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    - type: is-json</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      value:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        type: object</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        required: [categoría, prioridad, resumen]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        properties:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">          categoría: { type: string, enum: [facturación, técnica, cuenta, otra] }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">          prioridad: { type: string, enum: [baja, media, alta] }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">          resumen: { type: string }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">tests:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - vars: { ticket: \"Me cobraron dos veces por mi instancia este mes.\" }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    assert: [{ type: icontains, value: facturación }]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - vars: { ticket: \"Mi VM no arranca después de la última restauración de instantánea.\" }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    assert: [{ type: icontains, value: técnica }]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">EOF</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">npx </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-y</span><span class=\"token plain\"> promptfoo@latest </span><span class=\"token builtin class-name\">eval</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> promptfooconfig.yaml --no-cache</span><br></div></code></pre></div></div>\n<p>Sigue siendo <strong>4/8</strong> — los mismos modelos fallan. Así que <code>response_format</code> <strong>no cambió nada</strong>: en WEC hoy es efectivamente un no-op (el backend no está haciendo cumplir el modo JSON). El comportamiento de razonamiento pertenece a los modelos, no a la bandera.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Paso 5 — mismo 4/8 sin response_format, demostrando que fue un no-op\" src=\"https://wec.wiline.com/docs/es/assets/images/json-matrix-transform-77179c366ad47c9eb5b00fe2b5d92381.png\" width=\"824\" height=\"983\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 5.</strong> Resultado idéntico sin modo JSON — la bandera no estaba haciendo nada.</p>\n<div class=\"theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z\"></path></svg></span>nota</div><div class=\"admonitionContent_BuS1\"><p>Lección que merece su propio llamado: <strong>no atribuyas un resultado a un cambio hasta que hayas probado el cambio en aislamiento.</strong> Una ejecución adicional convirtió una conclusión incorrecta en la correcta.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-6--una-transformación-más-robusta-extraer-el-json\">Paso 6 — Una transformación más robusta: extraer el JSON<a href=\"https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/#paso-6--una-transformaci%C3%B3n-m%C3%A1s-robusta-extraer-el-json\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 6 — Una transformación más robusta: extraer el JSON\" title=\"Enlace directo al Paso 6 — Una transformación más robusta: extraer el JSON\" translate=\"no\">​</a></h2>\n<p>Los modelos de razonamiento <em>sí</em> emiten JSON — simplemente está enterrado en prosa. En lugar de eliminar cercas,\n<strong>extrae el primer bloque <code>{ … }</code></strong> de cualquier parte de la salida:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">cat</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> promptfooconfig.yaml </span><span class=\"token operator\" style=\"color:#393A34\">&lt;&lt;</span><span class=\"token string\" style=\"color:#e3116c\">'EOF'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">description: \"API de Inferencia WEC — transformación de extracción de JSON\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">providers:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - id: openai:chat:zai-org/GLM-5.2</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    config: { apiBaseUrl: https://inference.wiline.com/v1, apiKeyEnvar: WEC_API_KEY, temperature: 0 }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - id: openai:chat:gemma4</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    config: { apiBaseUrl: https://inference.wiline.com/v1, apiKeyEnvar: WEC_API_KEY, temperature: 0 }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - id: openai:chat:Qwen2.5-3B-Instruct</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    config: { apiBaseUrl: https://inference.wiline.com/v1, apiKeyEnvar: WEC_API_KEY, temperature: 0 }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - id: openai:chat:qwen3.5:9B</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    config: { apiBaseUrl: https://inference.wiline.com/v1, apiKeyEnvar: WEC_API_KEY, temperature: 0 }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">prompts:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - |</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    Clasifica este ticket de soporte. Devuelve SOLO JSON con claves:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    \"categoría\" (una de: facturación, técnica, cuenta, otra),</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    \"prioridad\" (una de: baja, media, alta),</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    \"resumen\" (cadena, máximo 12 palabras).</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    Ticket: {{ticket}}</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">defaultTest:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  options:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    transform: |</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      const m = output.match(/\\{[\\s\\S]*\\}/);</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      return m ? m[0] : output;</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  assert:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    - type: is-json</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      value:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        type: object</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        required: [categoría, prioridad, resumen]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        properties:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">          categoría: { type: string, enum: [facturación, técnica, cuenta, otra] }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">          prioridad: { type: string, enum: [baja, media, alta] }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">          resumen: { type: string }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">tests:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - vars: { ticket: \"Me cobraron dos veces por mi instancia este mes.\" }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    assert: [{ type: icontains, value: facturación }]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - vars: { ticket: \"Mi VM no arranca después de la última restauración de instantánea.\" }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    assert: [{ type: icontains, value: técnica }]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">EOF</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">npx </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-y</span><span class=\"token plain\"> promptfoo@latest </span><span class=\"token builtin class-name\">eval</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> promptfooconfig.yaml --no-cache</span><br></div></code></pre></div></div>\n<p><strong>7/8</strong> — un gran salto. gemma4 y Qwen2.5 recuperan completamente; qwen3.5:9B recupera uno de dos. El\núnico fallo restante es revelador: esa respuesta emitió JSON <strong>y luego siguió razonando</strong> (con\nmás llaves), por lo que la coincidencia codiciosa <code>{ … }</code> capturó en exceso y produjo JSON inválido.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Paso 6 — la extracción de JSON rescata la mayoría de los modelos (7/8)\" src=\"https://wec.wiline.com/docs/es/assets/images/json-matrix-extraction-3b34ee4a32f6990e447d9590d51d8911.png\" width=\"827\" height=\"901\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 6.</strong> Extraer el bloque JSON recupera los modelos habladores — pero es una heurística, no una garantía (nota el único fallo por captura excesiva).</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-7--verlo-en-el-navegador\">Paso 7 — Verlo en el navegador<a href=\"https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/#paso-7--verlo-en-el-navegador\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 7 — Verlo en el navegador\" title=\"Enlace directo al Paso 7 — Verlo en el navegador\" translate=\"no\">​</a></h2>\n<p>La tabla del terminal está bien para una lectura rápida, pero el informe web es donde una matriz como esta cobra vida:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">npx </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-y</span><span class=\"token plain\"> promptfoo@latest view</span><br></div></code></pre></div></div>\n<p>El panel en la parte superior te da la imagen de fiabilidad del modelo de un vistazo — las barras de <strong>Tasa de Aprobación (%)</strong> son las que debes observar aquí: tres modelos altos, <code>qwen3.5:9B</code> sentado en 50%.\nA continuación, cada proveedor es una columna que puedes expandir para ver la <strong>salida completa</strong> (así es como obtuvimos el fallo por captura excesiva en <a href=\"https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/#ts-reasoning-dump\" class=\"\">Figura 10</a> — el terminal lo trunca).</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El informe de Promptfoo: barras de Tasa de Aprobación por modelo y salida por caso expandible\" src=\"https://wec.wiline.com/docs/es/assets/images/json-view-report-3739e5abd8419ffc8ece8271ff559851.png\" width=\"1896\" height=\"1034\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 7.</strong> El panel del informe — las barras de Tasa de Aprobación convierten la matriz de modelos en un ranking de un vistazo, y cualquier celda se expande a la salida completa.</p>\n<div class=\"theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z\"></path></svg></span>nota</div><div class=\"admonitionContent_BuS1\"><p>Los otros gráficos del informe (el dispersor de aviso frente a aviso) brillan cuando estás <strong>A/B-testando\navisos</strong>, no modelos — un buen tema para una publicación posterior. Para una ejecución de un solo aviso y múltiples modelos como esta, las barras de Tasa de Aprobación son el gráfico que importa.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-8--bloquearlo-en-ci\">Paso 8 — Bloquearlo en CI<a href=\"https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/#paso-8--bloquearlo-en-ci\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 8 — Bloquearlo en CI\" title=\"Enlace directo al Paso 8 — Bloquearlo en CI\" translate=\"no\">​</a></h2>\n<p>Promptfoo sale con un código no cero cuando las pruebas fallan, así que una línea es una puerta (ponlo en un script o\n<code>Makefile</code>, nunca en tu shell interactivo):</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">npx </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-y</span><span class=\"token plain\"> promptfoo@latest </span><span class=\"token builtin class-name\">eval</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> promptfooconfig.yaml </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token operator\" style=\"color:#393A34\">&amp;&amp;</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">echo</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"✅ aprobado\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token operator\" style=\"color:#393A34\">||</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">echo</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"❌ evaluación fallida — bloqueando\"</span><span class=\"token punctuation\" style=\"color:#393A34\">;</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">exit</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">1</span><span class=\"token punctuation\" style=\"color:#393A34\">;</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"La puerta bloqueando en una evaluación fallida\" src=\"https://wec.wiline.com/docs/es/assets/images/json-ci-gate-71912b506157c96cfd7daa8e5c81f300.png\" width=\"948\" height=\"977\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 8.</strong> Un fallo real bloquea: 7/8 aprobados, pero la captura excesiva de <code>qwen3.5:9B</code> (del Paso 6)\ndispara la puerta — el código de salida no cero es la señal que CI usa para bloquear un PR.</p>\n<p>En GitLab:</p>\n<div class=\"language-yaml codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-yaml codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token key atrule\" style=\"color:#00a4db\">eval</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">image</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> node</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token number\" style=\"color:#36acaa\">22</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">script</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\"> npx </span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">y promptfoo@latest eval </span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">c promptfooconfig.yaml</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># WEC_API_KEY configurada como una variable CI/CD enmascarada — nunca comprometida.</span><br></div></code></pre></div></div>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Habilidad desbloqueada 🏅</div><div class=\"admonitionContent_BuS1\"><p><strong>JSON validado por esquema es ahora una puerta CI</strong> — un cambio en el modelo o aviso que rompe el análisis nunca se envía.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"lo-que-aprendimos\">Lo que aprendimos<a href=\"https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/#lo-que-aprendimos\" class=\"hash-link\" aria-label=\"Enlace directo al Lo que aprendimos\" title=\"Enlace directo al Lo que aprendimos\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\"><strong>La elección del modelo domina.</strong> <code>GLM-5.2</code> y <code>Qwen2.5-3B</code> producen JSON válido por esquema de manera fiable\ny son ~8× más baratos que los modelos de razonamiento. Para salida estructurada en WEC, comienza allí.</li>\n<li class=\"\"><strong>Los modelos de razonamiento</strong> (<code>qwen3.5:9B</code>, y <code>gemma4</code> en el día que probamos) narran su pensamiento\ny cuestan mucho más — malas opciones para JSON estricto.</li>\n<li class=\"\"><strong>Un <code>transform</code> es tu red de seguridad</strong> — la eliminación de cercas recupera el caso común; la extracción de JSON\nrecupera la mayoría de las salidas habladoras — pero la extracción es una heurística que puede capturar en exceso.</li>\n<li class=\"\"><strong><code>response_format</code> es un no-op en WEC hoy</strong> — verificado al aislarlo. No confíes en ello;\nvalida en su lugar.</li>\n<li class=\"\"><strong>La evaluación es el punto.</strong> Cada una de estas sorpresas fue capturada automáticamente, antes de que\nllegara a producción.</li>\n</ul>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"solución-de-problemas\">Solución de problemas<a href=\"https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/#soluci%C3%B3n-de-problemas\" class=\"hash-link\" aria-label=\"Enlace directo al Solución de problemas\" title=\"Enlace directo al Solución de problemas\" translate=\"no\">​</a></h2>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"la-función-de-transformación-no-devolvió-un-valor\"><a id=\"ts-transform-return\"></a>\"La función de transformación no devolvió un valor\"<a href=\"https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/#la-funci%C3%B3n-de-transformaci%C3%B3n-no-devolvi%C3%B3-un-valor\" class=\"hash-link\" aria-label=\"Enlace directo al la-función-de-transformación-no-devolvió-un-valor\" title=\"Enlace directo al la-función-de-transformación-no-devolvió-un-valor\" translate=\"no\">​</a></h3>\n<p>Un <code>transform</code> de varias líneas se compila como un <strong>cuerpo de función</strong>, por lo que necesita un <code>return</code> explícito. <code>output.replace(...)</code> por sí solo no devuelve nada.</p>\n<div class=\"language-yaml codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-yaml codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token key atrule\" style=\"color:#00a4db\">options</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">transform</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">|</span><span class=\"token scalar string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token scalar string\" style=\"color:#e3116c\">    return output.replace(/.../g, '').trim();   # nota el `return`</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El error de retorno de transformación\" src=\"https://wec.wiline.com/docs/es/assets/images/json-ts-transform-return-764896c26bc05d65d37eac7808ee06fb.png\" width=\"827\" height=\"894\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 9.</strong> Sin <code>return</code>, Promptfoo da error en lugar de transformar.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"un-modelo-devuelve-razonamiento-alrededor-del-json\"><a id=\"ts-reasoning-dump\"></a>Un modelo devuelve razonamiento alrededor del JSON<a href=\"https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/#un-modelo-devuelve-razonamiento-alrededor-del-json\" class=\"hash-link\" aria-label=\"Enlace directo al un-modelo-devuelve-razonamiento-alrededor-del-json\" title=\"Enlace directo al un-modelo-devuelve-razonamiento-alrededor-del-json\" translate=\"no\">​</a></h3>\n<p>Si <code>is-json</code> falla con <strong>\"Se esperaba que la salida fuera JSON válido\"</strong> y la respuesta contiene una\nnarración de \"Proceso de Pensamiento…\" / \"Revisión Final…\", estás usando un modelo de razonamiento. A veces emite <em>solo</em> razonamiento; a veces — como a continuación — emite JSON válido y luego <strong>sigue hablando</strong>, por lo que incluso la extracción del Paso 6 captura en exceso y el análisis falla. Opciones: cambia a\n<code>GLM-5.2</code> / <code>Qwen2.5-3B</code>, o acepta que la transformación de extracción es un esfuerzo de buena fe, no una garantía.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El caso fallido expandido en el informe — JSON seguido de razonamiento adicional\" src=\"https://wec.wiline.com/docs/es/assets/images/json-ts-reasoning-dump-dbfebf9e8d4f5f89e6b2e14b8e0edf50.png\" width=\"561\" height=\"859\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 10.</strong> El caso fallido expandido en el informe: <code>qwen3.5:9B</code> devolvió JSON válido y\n<em>después siguió razonando</em> (\"7. Revisión Final…\"), por lo que la extracción capturó en exceso y la afirmación informa \"Se esperaba que la salida fuera JSON válido.\"</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"no-se-puede-convertir-el-argumento-a-un-bytestring\"><a id=\"ts-invisible-key\"></a>\"No se puede convertir el argumento a un ByteString\"<a href=\"https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/#no-se-puede-convertir-el-argumento-a-un-bytestring\" class=\"hash-link\" aria-label=\"Enlace directo al no-se-puede-convertir-el-argumento-a-un-bytestring\" title=\"Enlace directo al no-se-puede-convertir-el-argumento-a-un-bytestring\" translate=\"no\">​</a></h3>\n<p>Tu clave de API pegada contiene un carácter Unicode invisible. Elimínalo:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token builtin class-name\">export</span><span class=\"token plain\"> </span><span class=\"token assign-left variable\" style=\"color:#36acaa\">WEC_API_KEY</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token variable\" style=\"color:#36acaa\">$(</span><span class=\"token variable builtin class-name\" style=\"color:#36acaa\">printf</span><span class=\"token variable\" style=\"color:#36acaa\"> </span><span class=\"token variable string\" style=\"color:#e3116c\">'%s'</span><span class=\"token variable\" style=\"color:#36acaa\"> </span><span class=\"token variable string\" style=\"color:#e3116c\">\"</span><span class=\"token variable string variable\" style=\"color:#36acaa\">$WEC_API_KEY</span><span class=\"token variable string\" style=\"color:#e3116c\">\"</span><span class=\"token variable\" style=\"color:#36acaa\"> </span><span class=\"token variable operator\" style=\"color:#393A34\">|</span><span class=\"token variable\" style=\"color:#36acaa\"> </span><span class=\"token variable assign-left variable environment constant\" style=\"color:#36acaa\">LC_ALL</span><span class=\"token variable operator\" style=\"color:#393A34\">=</span><span class=\"token variable\" style=\"color:#36acaa\">C </span><span class=\"token variable function\" style=\"color:#d73a49\">tr</span><span class=\"token variable\" style=\"color:#36acaa\"> </span><span class=\"token variable parameter variable\" style=\"color:#36acaa\">-cd</span><span class=\"token variable\" style=\"color:#36acaa\"> </span><span class=\"token variable string\" style=\"color:#e3116c\">'[:print:]'</span><span class=\"token variable\" style=\"color:#36acaa\">)</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El error de ByteString de un carácter clave invisible\" src=\"https://wec.wiline.com/docs/es/assets/images/json-ts-bytestring-0bb4979ce94f0775f1fe314315b38e01.png\" width=\"953\" height=\"988\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 11.</strong> Un carácter extraño en la clave activa el encabezado HTTP antes de que se envíe cualquier solicitud.</p>\n<div class=\"skillComplete\"><span class=\"skillComplete__medal\" aria-hidden=\"true\">🎯</span><div class=\"skillComplete__text\"><div class=\"skillComplete__title\">Finished this tutorial?</div><div class=\"skillComplete__sub\">Mark it complete to earn <strong>Trustworthy JSON</strong> on your skill path.</div></div><button type=\"button\" class=\"skillComplete__btn\">Mark as complete ✓</button></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"qué-sigue\">¿Qué sigue?<a href=\"https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/#qu%C3%A9-sigue\" class=\"hash-link\" aria-label=\"Enlace directo al ¿Qué sigue?\" title=\"Enlace directo al ¿Qué sigue?\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\"><strong>Conjuntos de regresión DeepEval</strong> — convierte estas verificaciones en una ejecución de regresión versionada y con puerta CI.</li>\n<li class=\"\"><strong>Trazado</strong> — ve <em>por qué</em> una respuesta salió mal, no solo que falló.</li>\n</ul>\n<p>¿Tienes un caso de salida estructurada que aún se escapa? Esa es la próxima prueba que agregar.</p>",
            "url": "https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/",
            "title": "JSON confiable: valida el esquema de la salida estructurada de tu modelo",
            "summary": "Los LLM prometen JSON y entregan cercas de markdown y razonamiento. Construye una evaluación de Promptfoo que clasifique tickets de soporte en JSON validado por esquema en la API de Inferencia WEC — con transformaciones para recuperar salidas desordenadas, una matriz de fiabilidad del modelo y una puerta CI. Cada comando y resultado es real.",
            "date_modified": "2026-07-01T00:00:00.000Z",
            "author": {
                "name": "Rafael Fernandes",
                "url": "https://www.linkedin.com/in/rafaelmacariofernandes/"
            },
            "tags": [
                "ai",
                "evals",
                "promptfoo",
                "inference",
                "json",
                "structured-output",
                "observability"
            ]
        },
        {
            "id": "https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/",
            "content_html": "\n<div class=\"tutorialHero\"><img class=\"tutorialHero__brand\" src=\"https://wec.wiline.com/docs/es/assets/images/promptfoo-avatar-31b32c657d72dac0a7edbd77c24ec717.jpg\" alt=\"Promptfoo\"><span class=\"tutorialHero__plus\">+</span><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"242\" height=\"255\" fill=\"none\" viewBox=\"0 0 242 255\" class=\"tutorialHero__wiline\"><path fill=\"#4E8EF7\" d=\"M56.711 11.293A195 195 0 0 1 41.899 4.79c4.788 1.85 9.71 4.028 14.812 6.504M73.807 29.35c-1.302-1.446-2.666-3.233-4.02-5.38 1.326 1.44 2.642 3.24 4.02 5.38M69.761 23.354c-.832-.585-1.752-1.472-2.558-2.714.9.567 1.685 1.49 2.558 2.714M62.97 15.774a10.7 10.7 0 0 1-3.158-1.841c.993.299 1.986.956 3.159 1.841M59.715 13.346c-.818-.194-1.782-.674-2.829-1.494.84.175 1.761.691 2.83 1.494M39.317 3.782c-.524.192-1.413.302-2.455.14.594-.255 1.342-.238 2.455-.14M74.906 31.339c-.334-.153-.73-.61-1.117-1.398.358.143.706.619 1.117 1.398M66.024 18.878c-.239.1-.557.05-.752-.276.35-.201.532-.069.752.276M71.773 150.625c3.363 7.291 7.185 14.411 9.983 21.914 4.474 11.997 11.167 20.832 24.296 24.757 10.62 3.175 20.369 1.795 30.093-1.816 9.937-3.689 15.165-11.978 18.501-21.541 4.349-12.466 9.385-24.443 19.668-33.446 11.32-9.909 24.639-14.788 39.429-15.63 8.3-.473 16.649-.081 25.444.444.472 15.673.385 30.825.55 45.974.038 3.499-1.049 4.719-4.557 4.543-4.982-.25-9.987-.079-14.98.009-.209.004-.4.991-.6 1.521-23.106 1.695-37.691 15.22-47.961 34.651-3.387 6.408-5.913 13.38-10.013 19.273-4.368 6.277-10.134 11.582-15.853 17.196-6.439 1.421-12.244 3.342-18.202 4.416-12.627 2.274-24.661.457-35.764-6.356-1.028-.631-2.395-.709-3.604-1.043 0 0-.117-.11-.047-.495-.818-.973-1.706-1.562-2.593-2.15 0 0 .09-.044.05-.335-.513-.545-.988-.798-1.463-1.052 0 0-.07-.123.012-.492-.561-.894-1.205-1.421-1.848-1.947 0 0 .134-.068.102-.28-.321-.442-.61-.671-.898-.901 0 0 .086-.007.074-.336-2.05-3.429-4.089-6.529-6.127-9.629 0 0 .094-.017.123-.365-3.013-6.117-6.056-11.886-9.1-17.655 0 0 .096-.01.088-.342-1.978-4.073-3.89-7.848-5.997-11.51-.246-.428-1.572-.235-2.398-.329 0 0-.226-.202-.114-.613-.213-.499-.537-.587-.861-.674 0 0-.032-.101.02-.455-.62-.846-1.291-1.339-1.964-1.832 0 0 .133-.083.119-.372-2.476-4.624-5.645-8.048-10.914-8.777 0 0 .108-.031.01-.326-1.673-1.444-3.105-2.918-4.85-3.688-9.751-4.308-20.013-5.643-30.63-5.23-7.305.285-7.316.051-7.317-7.446q-.002-18.496-.002-36.992v-3.57h36.106s-.08.019.133.283c1.43.415 2.646.567 3.863.719 0 0-.077.022.113.296 1.804.591 3.417.908 5.03 1.225 0 0 .139-.037.152.369 1.592.962 3.17 1.517 4.748 2.073 0 0-.091.027.052.267.735.415 1.328.59 1.92.765 0 0-.076.033.053.246.477.325.825.438 1.173.551 0 0-.149.018-.07.246.418.239.757.251 1.096.263 0 0 .23.058.189.465 1.522 1.319 3.084 2.231 4.647 3.143 0 0-.105.033-.015.316 2.723 2.764 5.356 5.244 7.989 7.724 0 0-.104.062-.094.352 1.01 1.401 2.01 2.512 3.01 3.624\"></path><path fill=\"#4E8EF7\" d=\"M214.77 52.819c-3.343.846-6.723 1.574-10.024 2.562-11.388 3.408-20.567 10.142-27.166 19.865-5.097 7.511-9.621 15.483-13.685 23.608-6.809 13.613-15.725 24.809-31.343 28.198-24.707 5.362-45.22-.126-57.852-24.564C69.466 92.363 63.8 82.375 57.349 73c-3.369-4.896-8.866-8.326-13.679-13.041-1.11-1.409-1.81-2.472-2.794-2.937-10.697-5.056-21.87-7.3-33.686-5.344-3.877.641-5.72-.297-5.615-4.86.294-12.816.26-25.646.019-38.464C1.509 3.874 3.15 2.5 7.429 2.716c9.774.492 19.564.678 29.347.989 0 0-.076.026.082.257 1.046.122 1.935.012 2.825-.098.575.043 1.15.086 2.038.692 5.265 2.72 10.218 4.877 15.17 7.034 0 0-.108.03-.016.317 1.058.765 2.022 1.245 2.987 1.724 0 0-.113.048-.082.354 1.144.876 2.258 1.446 3.372 2.017 0 0 .18.145.097.506.223.404.53.447.837.49 0 0 .214.13.144.504.244.426.558.477.873.528 0 0 .171.124.108.485.256.412.574.464.893.515 0 0 .167.127.105.493.265.437.592.509.919.58 0 0 .078.108.019.5.861 1.28 1.782 2.166 2.702 3.053 0 0-.101.061-.087.368 1.379 2.093 2.743 3.88 4.107 5.666 0 0-.094.011-.092.297.4.742.795 1.2 1.19 1.656 0 0-.121.02-.165.337.685 1.432 1.413 2.549 2.14 3.665 0 0-.11.024-.129.299.326.776.67 1.277 1.014 1.78 0 0-.084-.005-.1.367.933 4.079.311 8.497 4.648 10.67 4.69 12.164 12.107 21.794 25.852 23.497 19.618 2.431 35.321.062 45.299-24.554 3.398-8.383 7.099-16.902 12.232-24.264 8.353-11.979 21.046-17.795 35.832-20.32 1.666.417 2.649.651 3.624.622 9.93-.298 19.865-.513 29.785-1.032 3.818-.2 4.825 1.198 4.746 4.853-.218 9.967.004 19.944-.109 29.915-.052 4.583 1.149 10.606-1.239 13.302-2.044 2.308-8.394.86-12.842.967-3.82.092-7.644.036-11.467.045.065.31.097.633.213.922.034.084.306.072.469.105M37.585 127.354c-11.585.344-23.368.344-35.907.344v3.57q0 18.496.002 36.992c0 7.497.012 7.731 7.318 7.446 10.616-.413 20.878.922 30.63 5.23 1.743.77 3.176 2.244 4.84 3.744-13.655-7.873-28.93-7.767-44.468-7.398v-52.503c12.516.308 25.063-1.222 37.585 2.575\"></path><path fill=\"#4E8EF7\" d=\"M36.619 3.401c-9.626-.007-19.415-.193-29.19-.685C3.151 2.5 1.51 3.874 1.594 8.354c.241 12.818.275 25.648-.018 38.464-.105 4.563 1.737 5.501 5.614 4.86 11.816-1.956 22.99.288 33.686 5.344.984.465 1.685 1.529 2.616 2.66-13.39-7.538-28.284-7.343-43.479-6.78V0C12.314 1.045 24.388 2.071 36.62 3.401M215.232 52.852c-.625-.066-.897-.054-.931-.138-.116-.29-.148-.612-.212-.922 3.822-.01 7.646.047 11.467-.045 4.448-.108 10.797 1.341 12.842-.967 2.387-2.696 1.186-8.719 1.238-13.302.114-9.971-.109-19.948.109-29.915.08-3.655-.928-5.052-4.746-4.853-9.919.519-19.855.734-29.785 1.032-.975.03-1.958-.205-3.228-.58C214.559 1.945 227.423.991 240.73.003v52.882c-8.276 0-16.656 0-25.498-.033M220.071 177.398c-.271-.573-.08-1.561.129-1.565 4.993-.088 9.998-.259 14.98-.009 3.508.176 4.595-1.044 4.557-4.543-.165-15.149-.078-30.301-.088-45.911.458.372 1.32 1.202 1.324 2.036.079 16.424.059 32.848.059 50.036-6.984 0-13.737 0-20.961-.044M88.39 245.741c1.022.084 2.39.162 3.417.793 11.102 6.813 23.137 8.63 35.763 6.356 5.959-1.073 11.764-2.995 17.871-4.34-5.612 2.132-11.343 5.166-17.325 5.79-13.898 1.451-27.803 1.213-39.725-8.599M66.556 210.212c2.975 5.411 6.018 11.18 9.02 17.344-3.011-5.4-5.982-11.193-9.02-17.344M58.341 197.934c.666-.167 1.992-.36 2.238.068 2.107 3.662 4.018 7.437 5.985 11.557-2.69-3.535-5.376-7.449-8.223-11.625M44.637 185.241c5.099.438 8.268 3.862 10.737 8.564-3.518-2.513-7.043-5.393-10.737-8.564M82.29 48.422c-4.252-1.834-3.63-6.252-4.548-10.37 1.508 3.069 2.986 6.55 4.548 10.37M68.757 146.326c-2.533-2.157-5.166-4.637-7.882-7.455 2.539 2.151 5.16 4.641 7.882 7.455M75.532 228.226a122 122 0 0 1 6.047 9.319c-1.993-2.742-3.986-5.854-6.047-9.319M71.695 150.312c-.922-.799-1.921-1.91-2.906-3.366.952.788 1.89 1.921 2.906 3.366M46.663 130.007c-1.353-.103-2.966-.42-4.767-1.051 1.377.07 2.941.454 4.767 1.051M51.647 132.369a13.1 13.1 0 0 1-4.501-1.77c1.48.227 2.902.852 4.5 1.77M60.724 138.321a17.7 17.7 0 0 1-4.395-2.781c1.501.609 2.869 1.551 4.395 2.781M76.889 35.31c-.674-.781-1.402-1.898-2.068-3.39.711.768 1.362 1.912 2.068 3.39M85.668 243.124c.782.31 1.67.899 2.423 1.825-.862-.29-1.59-.919-2.423-1.825M41.596 128.372c-1.033.176-2.25.024-3.68-.432 1.023-.169 2.26-.032 3.68.432M55.367 194.37c.568.222 1.24.715 1.808 1.548-.637-.199-1.17-.738-1.808-1.548M82.397 239.295c.56.251 1.204.778 1.71 1.628-.633-.235-1.13-.794-1.71-1.628M84.291 241.679c.334.034.809.287 1.298.875-.376.004-.766-.326-1.298-.875M53.64 133.426c-.438.094-1.03-.081-1.764-.543.441-.101 1.026.086 1.764.543M77.805 37.409c-.322-.188-.666-.69-.978-1.52.34.182.648.693.978 1.52M81.588 238.108c.218-.039.507.19.823.707-.233.045-.493-.196-.823-.707M55.906 134.832c-.198.157-.537.145-.92-.161.262-.29.537-.195.92.161M57.276 196.552c.254-.079.578.009.756.361-.377.171-.557.018-.756-.361M54.888 134.25c-.215.129-.563.017-1.038-.351.217-.133.56-.012 1.038.351M64.014 16.858a.545.545 0 0 1-.71-.247c.344-.19.51-.067.71.247M65.027 17.882a.574.574 0 0 1-.734-.272c.35-.193.525-.063.734.272M67.045 19.939c-.244.093-.57.021-.777-.329.358-.192.55-.044.777.329\"></path></svg></div>\n<div class=\"skillTracker\" tabindex=\"0\" aria-label=\"Skill path: 0 of 8 skills earned\"><div class=\"skillTracker__ring\" style=\"--skillTracker-pct:0%\"><span class=\"skillTracker__ringLabel\">0<!-- -->/<!-- -->8</span></div><div class=\"skillTracker__panel\"><div class=\"skillTracker__header\"><span class=\"skillTracker__title\">🎯 Skill path</span><span class=\"skillTracker__count\">0<!-- -->/<!-- -->8<!-- --> earned</span></div><div class=\"skillTracker__series\">AI evals &amp; observability</div><ul class=\"skillTracker__steps\"><li><span class=\"skillTracker__step\"><span class=\"skillTracker__dot\" data-state=\"current\">1</span><span class=\"skillTracker__skill\" data-state=\"current\">Prove a model works</span></span></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">2</span><span class=\"skillTracker__skill\" data-state=\"locked\">Trustworthy JSON</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/synthetic-eval-datasets-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">3</span><span class=\"skillTracker__skill\" data-state=\"locked\">Real test data at scale</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/observe-production-langfuse-wiline/\"><span class=\"skillTracker__dot\" data-state=\"locked\">4</span><span class=\"skillTracker__skill\" data-state=\"locked\">Observe &amp; score production</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/rag-docs-assistant-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">5</span><span class=\"skillTracker__skill\" data-state=\"locked\">RAG, end to end</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/deepeval-regression-testing-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">6</span><span class=\"skillTracker__skill\" data-state=\"locked\">Catch regressions in CI</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/component-tracing-agent-tool-calls-wiline/\"><span class=\"skillTracker__dot\" data-state=\"locked\">7</span><span class=\"skillTracker__skill\" data-state=\"locked\">Trace &amp; debug agent tool calls</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/web-search-wiline-inference/\"><span class=\"skillTracker__dot\" data-state=\"locked\">🏆</span><span class=\"skillTracker__skill\" data-state=\"locked\">Add live web search</span></a></li></ul><div class=\"skillTracker__footer\">Finish this tutorial, then mark it complete below to earn the skill.</div></div></div>\n<p>No enviarías código sin pruebas — pero la mayoría de los equipos envían funciones LLM con \"se ve bien para mí.\" <strong>Evaluaciones</strong> (evals) solucionan eso: defines casos de prueba y criterios de aprobación/fallo, luego mides tu modelo objetivamente — detectando regresiones, comparando modelos y bloqueando despliegues.</p>\n<p>En esta guía construirás un verdadero arnés de evaluación con <a href=\"https://www.promptfoo.dev/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Promptfoo</a> apuntando a la <strong>API de Inferencia WEC</strong> — el mismo punto final compatible con OpenAI que llamas desde tus aplicaciones. Todo aquí se ejecutó en vivo contra <code>https://inference.wiline.com</code>; las salidas son reales.</p>\n<!-- -->\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"requisitos-previos\">Requisitos previos<a href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/#requisitos-previos\" class=\"hash-link\" aria-label=\"Enlace directo al Requisitos previos\" title=\"Enlace directo al Requisitos previos\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\">\n<p>Una cuenta de <strong>WEC (WiLine Edge Cloud)</strong> y una <strong>clave API de Inferencia</strong> — consulta <a class=\"\" href=\"https://wec.wiline.com/docs/es/cloud_portal/platform/inference/api_keys/\">Inferencia → Claves API</a>.</p>\n</li>\n<li class=\"\">\n<p><strong>Node.js 22+</strong>. Promptfoo considera que Node 20 está al final de su vida útil; con <code>nvm</code>:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">nvm </span><span class=\"token function\" style=\"color:#d73a49\">install</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">24</span><span class=\"token plain\">      </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># 24 LTS preferido</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">nvm use </span><span class=\"token number\" style=\"color:#36acaa\">24</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">node</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-v</span><span class=\"token plain\">             </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># v24.x</span><br></div></code></pre></div></div>\n</li>\n</ul>\n<p>Ejecutaremos Promptfoo con <code>npx</code> — nada que instalar globalmente.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"La página de claves API del portal WiLine\" src=\"https://wec.wiline.com/docs/es/assets/images/promptfoo-api-key-a1aca6bfffa7335651ab59ec1c07d8f4.png\" width=\"1642\" height=\"866\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 1.</strong> Crea una clave en Inferencia → Claves API, luego cópiala.</p>\n<p>Pon tu clave en la terminal:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token builtin class-name\">export</span><span class=\"token plain\"> </span><span class=\"token assign-left variable\" style=\"color:#36acaa\">WEC_API_KEY</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token string\" style=\"color:#e3116c\">'sk-your-key-here'</span><br></div></code></pre></div></div>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>tip</div><div class=\"admonitionContent_BuS1\"><p>Si tu primera evaluación falla con <code>Cannot convert argument to a ByteString</code>, consulta <a href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/#1-bytestring-error-on-first-eval\" class=\"\">Solución de problemas #1</a> a continuación.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-1--ver-qué-modelos-tienes\">Paso 1 — Ver qué modelos tienes<a href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/#paso-1--ver-qu%C3%A9-modelos-tienes\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 1 — Ver qué modelos tienes\" title=\"Enlace directo al Paso 1 — Ver qué modelos tienes\" translate=\"no\">​</a></h2>\n<p>La API es compatible con OpenAI, por lo que la lista de modelos es un simple <code>GET /v1/models</code>:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-s</span><span class=\"token plain\"> https://inference.wiline.com/v1/models </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Authorization: Bearer </span><span class=\"token string variable\" style=\"color:#36acaa\">$WEC_API_KEY</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> jq </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-r</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'.data[].id'</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">zai-org/GLM-5.2</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">gemma4</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Qwen2.5-3B-Instruct</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">qwen3.5:9B</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">whisper-large-v3</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">whisper-medium</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">whisper-base</span><br></div></code></pre></div></div>\n<p>Los modelos <code>whisper-*</code> son de voz a texto; el resto son modelos de chat. Evaluaremos los modelos de chat, comenzando con <code>zai-org/GLM-5.2</code>.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-2--tu-primera-evaluación\">Paso 2 — Tu primera evaluación<a href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/#paso-2--tu-primera-evaluaci%C3%B3n\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 2 — Tu primera evaluación\" title=\"Enlace directo al Paso 2 — Tu primera evaluación\" translate=\"no\">​</a></h2>\n<p>Crea una configuración. <code>apiBaseUrl</code> apunta Promptfoo a WEC; <code>apiKeyEnvar</code> le dice qué variable de entorno contiene tu clave:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">cat</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> promptfooconfig.yaml </span><span class=\"token operator\" style=\"color:#393A34\">&lt;&lt;</span><span class=\"token string\" style=\"color:#e3116c\">'EOF'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">description: \"Evaluación de API de Inferencia WEC\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">providers:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - id: openai:chat:zai-org/GLM-5.2</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    config:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      apiBaseUrl: https://inference.wiline.com/v1</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      apiKeyEnvar: WEC_API_KEY</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      temperature: 0</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">prompts:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - \"Responde en una sola palabra. ¿Cuál es la capital de {{country}}?\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">tests:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - vars: { country: France }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    assert: [{ type: icontains, value: Paris }]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - vars: { country: Japan }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    assert: [{ type: icontains, value: Tokyo }]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">EOF</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">npx </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-y</span><span class=\"token plain\"> promptfoo@latest </span><span class=\"token builtin class-name\">eval</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> promptfooconfig.yaml</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Terminal: primera evaluación, 2 casos ambos APROBADOS\" src=\"https://wec.wiline.com/docs/es/assets/images/promptfoo-first-eval-42c0a9a515e6a1e5af7a6cf5c6a30118.png\" width=\"954\" height=\"501\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 4.</strong> Tu primera evaluación aprobada contra la API de Inferencia WEC.</p>\n<p><code>icontains</code> es una afirmación <strong>determinista</strong> — una verificación de subcadena que no distingue entre mayúsculas y minúsculas. Barata, rápida y perfecta para hechos con una respuesta conocida.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-3--el-informe-visual\">Paso 3 — El informe visual<a href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/#paso-3--el-informe-visual\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 3 — El informe visual\" title=\"Enlace directo al Paso 3 — El informe visual\" translate=\"no\">​</a></h2>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">npx </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-y</span><span class=\"token plain\"> promptfoo@latest view</span><br></div></code></pre></div></div>\n<p>Esto abre una interfaz web local con cada aviso, salida y aprobación/fallo — mucho más fácil de escanear que la terminal una vez que tienes más de unos pocos casos.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El informe web de Promptfoo\" src=\"https://wec.wiline.com/docs/es/assets/images/promptfoo-view-report-b93aa9bcadc4c60e008df1bb5effcd3c.png\" width=\"1904\" height=\"1040\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 5.</strong> <code>promptfoo view</code> — los mismos resultados en el navegador.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-4--agregar-un-segundo-modelo\">Paso 4 — Agregar un segundo modelo<a href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/#paso-4--agregar-un-segundo-modelo\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 4 — Agregar un segundo modelo\" title=\"Enlace directo al Paso 4 — Agregar un segundo modelo\" translate=\"no\">​</a></h2>\n<p>Agregar un proveedor es un bloque adicional. Aquí agregamos <code>Qwen2.5-3B-Instruct</code> junto a GLM-5.2 y ejecutamos los mismos dos casos de prueba contra ambos:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">cat</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> promptfooconfig.yaml </span><span class=\"token operator\" style=\"color:#393A34\">&lt;&lt;</span><span class=\"token string\" style=\"color:#e3116c\">'EOF'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">description: \"Evaluación de API de Inferencia WEC — comparación de modelos\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">providers:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - id: openai:chat:zai-org/GLM-5.2</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    config:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      apiBaseUrl: https://inference.wiline.com/v1</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      apiKeyEnvar: WEC_API_KEY</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      temperature: 0</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - id: openai:chat:Qwen2.5-3B-Instruct</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    config:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      apiBaseUrl: https://inference.wiline.com/v1</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      apiKeyEnvar: WEC_API_KEY</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      temperature: 0</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">prompts:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - \"Responde en una sola palabra. ¿Cuál es la capital de {{country}}?\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">tests:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - vars: { country: France }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    assert: [{ type: icontains, value: Paris }]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - vars: { country: Japan }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    assert: [{ type: icontains, value: Tokyo }]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">EOF</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">npx </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-y</span><span class=\"token plain\"> promptfoo@latest </span><span class=\"token builtin class-name\">eval</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> promptfooconfig.yaml</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Terminal: comparación de 2 modelos, 4 casos todos APROBADOS\" src=\"https://wec.wiline.com/docs/es/assets/images/promptfoo-two-model-3c6c51ea8839c03dd51d270c78648ec9.png\" width=\"956\" height=\"590\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 6.</strong> Ambos modelos aprueban. Los resultados de GLM-5.2 se sirven desde caché (0 nuevas solicitudes); solo Qwen2.5 realiza llamadas en vivo.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-5--califica-calidad-no-subcadenas-rúbrica-llm\">Paso 5 — Califica calidad, no subcadenas (rúbrica LLM)<a href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/#paso-5--califica-calidad-no-subcadenas-r%C3%BAbrica-llm\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 5 — Califica calidad, no subcadenas (rúbrica LLM)\" title=\"Enlace directo al Paso 5 — Califica calidad, no subcadenas (rúbrica LLM)\" translate=\"no\">​</a></h2>\n<p>Las verificaciones de subcadenas no pueden juzgar una respuesta abierta. Una afirmación <code>llm-rubric</code> <strong>calificada por el modelo</strong> utiliza un modelo para puntuar la salida contra criterios — y el calificador puede ejecutarse en WEC también, por lo que no necesitas un proveedor externo:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">cat</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> promptfooconfig.yaml </span><span class=\"token operator\" style=\"color:#393A34\">&lt;&lt;</span><span class=\"token string\" style=\"color:#e3116c\">'EOF'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">description: \"Evaluación de API de Inferencia WEC — calificación por modelo (rúbrica LLM)\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">providers:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - id: openai:chat:zai-org/GLM-5.2</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    config:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      apiBaseUrl: https://inference.wiline.com/v1</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      apiKeyEnvar: WEC_API_KEY</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      temperature: 0</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">defaultTest:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  options:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    provider:                       # el modelo calificador</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      id: openai:chat:zai-org/GLM-5.2</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      config:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        apiBaseUrl: https://inference.wiline.com/v1</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        apiKeyEnvar: WEC_API_KEY</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">prompts:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - \"Explica qué es un {{topic}} en dos oraciones, para un principiante.\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">tests:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - vars: { topic: VPC }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    assert:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      - type: llm-rubric</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        value: \"Describe una VPC como una red virtual privada e aislada en la nube, y es comprensible para un principiante.\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">EOF</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">npx </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-y</span><span class=\"token plain\"> promptfoo@latest </span><span class=\"token builtin class-name\">eval</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> promptfooconfig.yaml</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Terminal: evaluación de rúbrica LLM, 1 APROBADO, línea de tokens de calificación visible\" src=\"https://wec.wiline.com/docs/es/assets/images/promptfoo-llm-rubric-f969ad98a43427eacf9f83f13f2e6dde.png\" width=\"957\" height=\"556\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 7.</strong> La ejecución muestra dos líneas de tokens — <code>Eval</code> (75) y <code>Grading</code> (350) — porque la verificación de rúbrica es en sí misma una llamada de modelo.</p>\n<p>Las verificaciones calificadas por el modelo son poderosas, pero no son gratuitas.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-6--líneas-de-defensa-latencia\">Paso 6 — Líneas de defensa: latencia<a href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/#paso-6--l%C3%ADneas-de-defensa-latencia\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 6 — Líneas de defensa: latencia\" title=\"Enlace directo al Paso 6 — Líneas de defensa: latencia\" translate=\"no\">​</a></h2>\n<p>Una respuesta correcta que toma 30 segundos sigue siendo un problema de producción. Agrega un límite de <strong>latencia</strong> a <code>defaultTest</code> (se aplica a cada caso). Siempre ejecuta con <code>--no-cache</code> — las respuestas en caché informan ~0 ms y pasarían la puerta gratis:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">cat</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> promptfooconfig.yaml </span><span class=\"token operator\" style=\"color:#393A34\">&lt;&lt;</span><span class=\"token string\" style=\"color:#e3116c\">'EOF'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">description: \"Evaluación de API de Inferencia WEC — límite de latencia\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">providers:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - id: openai:chat:zai-org/GLM-5.2</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    config:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      apiBaseUrl: https://inference.wiline.com/v1</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      apiKeyEnvar: WEC_API_KEY</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      temperature: 0</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">defaultTest:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  assert:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    - type: latency</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      threshold: 5000        # fallar cualquier cosa más lenta que 5 s</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">prompts:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - \"Responde en una sola palabra. ¿Cuál es la capital de {{country}}?\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">tests:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - vars: { country: France }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    assert: [{ type: icontains, value: Paris }]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - vars: { country: Japan }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    assert: [{ type: icontains, value: Tokyo }]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">EOF</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">npx </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-y</span><span class=\"token plain\"> promptfoo@latest </span><span class=\"token builtin class-name\">eval</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> promptfooconfig.yaml --no-cache</span><br></div></code></pre></div></div>\n<p>Esto <strong>falló</strong> — las respuestas eran correctas (<code>Paris</code>, <code>Tokyo</code>) pero ambas llamadas tomaron ~33 s, activando la puerta de 5 s:</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Terminal: latencia 5000ms umbral, 2 FALLIDOS, Duración: 33s\" src=\"https://wec.wiline.com/docs/es/assets/images/promptfoo-latency-fail-ab40e8dc4386c6acd496942ee3a490aa.png\" width=\"953\" height=\"518\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 8.</strong> La línea de defensa se activa: respuestas correctas, latencia incorrecta. Duración: 33s.</p>\n<p>Aumenta el umbral a algo realista y vuelve a ejecutar:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">sed</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-i</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'s/threshold: 5000/threshold: 60000/'</span><span class=\"token plain\"> promptfooconfig.yaml</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">npx </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-y</span><span class=\"token plain\"> promptfoo@latest </span><span class=\"token builtin class-name\">eval</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> promptfooconfig.yaml --no-cache</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Terminal: latencia 60000ms umbral, 2 APROBADOS, Duración: 2s\" src=\"https://wec.wiline.com/docs/es/assets/images/promptfoo-latency-pass-f3fa1a1c9cb0a11a87328b2a15ef1f31.png\" width=\"957\" height=\"538\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 9.</strong> Con un umbral de 60 s, las mismas llamadas pasan fácilmente — Duración: 2s.</p>\n<div class=\"theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z\"></path></svg></span>La latencia de inferencia varía — calibra antes de bloquear</div><div class=\"admonitionContent_BuS1\"><p>Las llamadas consecutivas al mismo modelo variaron de ~2 s a ~33 s en el mismo día. Ejecuta algunos pases <code>--no-cache</code> para obtener una línea base realista antes de fijar tu umbral.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-7--líneas-de-defensa-latencia--límite-de-tokens\">Paso 7 — Líneas de defensa: latencia + límite de tokens<a href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/#paso-7--l%C3%ADneas-de-defensa-latencia--l%C3%ADmite-de-tokens\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 7 — Líneas de defensa: latencia + límite de tokens\" title=\"Enlace directo al Paso 7 — Líneas de defensa: latencia + límite de tokens\" translate=\"no\">​</a></h2>\n<p>La afirmación de <code>cost</code> no funciona contra WEC (consulta <a href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/#2-cost-assertion-errors-immediately\" class=\"\">Solución de problemas #2</a>). Usa latencia + <code>max_tokens</code> juntos como tu línea de defensa de producción en su lugar:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">cat</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> promptfooconfig.yaml </span><span class=\"token operator\" style=\"color:#393A34\">&lt;&lt;</span><span class=\"token string\" style=\"color:#e3116c\">'EOF'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">description: \"Evaluación de API de Inferencia WEC — líneas de defensa (latencia + límite de tokens)\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">providers:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - id: openai:chat:zai-org/GLM-5.2</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    config:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      apiBaseUrl: https://inference.wiline.com/v1</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      apiKeyEnvar: WEC_API_KEY</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      temperature: 0</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      max_tokens: 16</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">defaultTest:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  assert:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    - type: latency</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      threshold: 30000</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">prompts:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - \"Responde en una sola palabra. ¿Cuál es la capital de {{country}}?\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">tests:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - vars: { country: France }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    assert: [{ type: icontains, value: Paris }]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - vars: { country: Japan }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    assert: [{ type: icontains, value: Tokyo }]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">EOF</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">npx </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-y</span><span class=\"token plain\"> promptfoo@latest </span><span class=\"token builtin class-name\">eval</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> promptfooconfig.yaml --no-cache</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Terminal: latencia 30000ms + max_tokens:16, 2 APROBADOS\" src=\"https://wec.wiline.com/docs/es/assets/images/promptfoo-latency-token-cap-968bf5b53dd3edf6d77129f011e4a66f.png\" width=\"955\" height=\"522\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 11.</strong> Con un umbral de 30 s y un límite de 16 tokens, ambos casos aprueban.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-8--validar-salida-estructurada-validación-de-esquema-json\">Paso 8 — Validar salida estructurada (validación de esquema JSON)<a href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/#paso-8--validar-salida-estructurada-validaci%C3%B3n-de-esquema-json\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 8 — Validar salida estructurada (validación de esquema JSON)\" title=\"Enlace directo al Paso 8 — Validar salida estructurada (validación de esquema JSON)\" translate=\"no\">​</a></h2>\n<p>Las aplicaciones reales quieren JSON, no prosa. <code>is-json</code> verifica que la salida sea un JSON válido <strong>y</strong> coincida con un esquema en una afirmación:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">cat</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> promptfooconfig.yaml </span><span class=\"token operator\" style=\"color:#393A34\">&lt;&lt;</span><span class=\"token string\" style=\"color:#e3116c\">'EOF'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">description: \"Evaluación de API de Inferencia WEC — validación de esquema JSON\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">providers:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - id: openai:chat:zai-org/GLM-5.2</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    config:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      apiBaseUrl: https://inference.wiline.com/v1</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      apiKeyEnvar: WEC_API_KEY</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      temperature: 0</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">prompts:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - 'Devuelve SOLAMENTE un objeto JSON (sin markdown, sin prosa) con claves \"capital\" (cadena) y \"population_millions\" (número) para el país {{country}}.'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">defaultTest:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  assert:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    - type: is-json</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      value:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        type: object</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        required: [capital, population_millions]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        properties:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">          capital: { type: string }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">          population_millions: { type: number }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">tests:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - vars: { country: France }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    assert: [{ type: icontains, value: Paris }]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - vars: { country: Japan }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    assert: [{ type: icontains, value: Tokyo }]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">EOF</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">npx </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-y</span><span class=\"token plain\"> promptfoo@latest </span><span class=\"token builtin class-name\">eval</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> promptfooconfig.yaml</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Terminal: evaluación de esquema JSON, Francia y Japón ambos APROBADOS con salida JSON limpia\" src=\"https://wec.wiline.com/docs/es/assets/images/promptfoo-json-schema-82dcc59881029f9bc0134dcf5a5c5ea2.png\" width=\"955\" height=\"516\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 12.</strong> GLM-5.2 devuelve JSON compacto y válido según el esquema en el primer intento.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-9--la-recompensa-compara-cada-modelo-wec\">Paso 9 — La recompensa: compara cada modelo WEC<a href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/#paso-9--la-recompensa-compara-cada-modelo-wec\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 9 — La recompensa: compara cada modelo WEC\" title=\"Enlace directo al Paso 9 — La recompensa: compara cada modelo WEC\" translate=\"no\">​</a></h2>\n<p>Aquí está lo que no puedes obtener en ningún otro lugar — la misma prueba estricta de JSON a través de <strong>todos</strong> los modelos de chat en tu cuenta:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">cat</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> promptfooconfig.yaml </span><span class=\"token operator\" style=\"color:#393A34\">&lt;&lt;</span><span class=\"token string\" style=\"color:#e3116c\">'EOF'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">description: \"Evaluación de API de Inferencia WEC — matriz de modelos\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">providers:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - id: openai:chat:zai-org/GLM-5.2</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    config: { apiBaseUrl: https://inference.wiline.com/v1, apiKeyEnvar: WEC_API_KEY, temperature: 0 }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - id: openai:chat:gemma4</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    config: { apiBaseUrl: https://inference.wiline.com/v1, apiKeyEnvar: WEC_API_KEY, temperature: 0 }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - id: openai:chat:Qwen2.5-3B-Instruct</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    config: { apiBaseUrl: https://inference.wiline.com/v1, apiKeyEnvar: WEC_API_KEY, temperature: 0 }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - id: openai:chat:qwen3.5:9B</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    config: { apiBaseUrl: https://inference.wiline.com/v1, apiKeyEnvar: WEC_API_KEY, temperature: 0 }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">prompts:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - 'Devuelve SOLAMENTE un objeto JSON (sin markdown, sin prosa) con claves \"capital\" (cadena) y \"population_millions\" (número) para el país {{country}}.'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">defaultTest:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  assert:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    - type: is-json</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      value:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        type: object</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        required: [capital, population_millions]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">        properties:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">          capital: { type: string }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">          population_millions: { type: number }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">tests:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - vars: { country: France }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    assert: [{ type: icontains, value: Paris }]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - vars: { country: Japan }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    assert: [{ type: icontains, value: Tokyo }]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  - vars: { country: Brazil }</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    assert: [{ type: icontains, value: Bras }]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">EOF</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">npx </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-y</span><span class=\"token plain\"> promptfoo@latest </span><span class=\"token builtin class-name\">eval</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> promptfooconfig.yaml</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Terminal: tabla de resultados de matriz de 4 modelos — GLM, gemma4, Qwen2.5 todos APROBADOS; qwen3.5:9B todos FALLIDOS con texto de razonamiento\" src=\"https://wec.wiline.com/docs/es/assets/images/promptfoo-model-matrix-table-fcebe135ddd276ef881fc83a726c6b96.png\" width=\"1001\" height=\"972\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 13a.</strong> 12 casos de prueba a través de 4 modelos — 9 aprobados, 3 fallidos. <code>qwen3.5:9B</code> emite su cadena de pensamiento en lugar de JSON.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Terminal: resumen de tokens de matriz de modelos — qwen3.5:9B consumió 2,650 tokens frente a ~200 para los demás\" src=\"https://wec.wiline.com/docs/es/assets/images/promptfoo-model-matrix-summary-96e3ce6b439ae8d487aef486120dbe97.png\" width=\"639\" height=\"319\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 13b.</strong> La descomposición de tokens cuenta la verdadera historia: el modelo de razonamiento costó 13× más para la misma tarea.</p>\n<table><thead><tr><th>Modelo</th><th>¿JSON Estricto?</th><th>Notas</th><th>Tokens (3 llamadas)</th></tr></thead><tbody><tr><td><code>zai-org/GLM-5.2</code></td><td>✅ todos aprueban</td><td>JSON compacto</td><td>177</td></tr><tr><td><code>gemma4</code></td><td>✅ todos aprueban</td><td>poblaciones enteras</td><td>207</td></tr><tr><td><code>Qwen2.5-3B-Instruct</code></td><td>✅ todos aprueban</td><td>JSON bien formateado, preciso</td><td>262</td></tr><tr><td><code>qwen3.5:9B</code></td><td>❌ todos fallan</td><td><strong>modelo de razonamiento</strong> — emite su \"pensamiento\" en lugar de JSON crudo</td><td><strong>2,650</strong></td></tr></tbody></table>\n<p>Dos lecciones se desprenden de inmediato:</p>\n<ul>\n<li class=\"\"><strong><code>qwen3.5:9B</code> es un modelo de razonamiento.</strong> \"Piensa en voz alta\", por lo que rompe el JSON estricto a menos que elimines el razonamiento o uses una restricción de formato de respuesta. Elegirlo para salida estructurada fallaría silenciosamente.</li>\n<li class=\"\"><strong>El costo varía ~13×.</strong> El modelo de razonamiento consumió 2,650 tokens para la misma tarea que los demás hicieron en ~200. Para un trabajo de extracción de JSON, los modelos más pequeños son correctos <em>y</em> mucho más baratos.</li>\n</ul>\n<p>Esa es una decisión de selección de modelo que ahora puedes defender con datos — para <em>tus</em> cargas de trabajo, no una tabla de clasificación genérica.</p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Habilidad desbloqueada 🏅</div><div class=\"admonitionContent_BuS1\"><p>Ahora puedes <strong>comparar cada modelo en tu punto final con números objetivos y repetibles</strong> — precisión, latencia y costo lado a lado.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-10--hazlo-una-puerta\">Paso 10 — Hazlo una puerta<a href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/#paso-10--hazlo-una-puerta\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 10 — Hazlo una puerta\" title=\"Enlace directo al Paso 10 — Hazlo una puerta\" translate=\"no\">​</a></h2>\n<p>Promptfoo sale <strong>no cero (<code>100</code>)</strong> cuando cualquier prueba falla. Verifica esto primero:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">npx </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-y</span><span class=\"token plain\"> promptfoo@latest </span><span class=\"token builtin class-name\">eval</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> promptfooconfig.yaml </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> /dev/null </span><span class=\"token operator file-descriptor important\" style=\"color:#393A34\">2</span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token file-descriptor important\">&amp;1</span><span class=\"token punctuation\" style=\"color:#393A34\">;</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">echo</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"código de salida: </span><span class=\"token string variable\" style=\"color:#36acaa\">$?</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Terminal: código de salida: 100\" src=\"https://wec.wiline.com/docs/es/assets/images/promptfoo-exit-code-a3471bde65e7ebf24feaf0d964a3b7eb.png\" width=\"652\" height=\"70\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 14.</strong> El código de salida 100 confirma que Promptfoo señala fallos de manera confiable — exactamente lo que necesita CI.</p>\n<p>Conéctalo como una puerta:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">npx </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-y</span><span class=\"token plain\"> promptfoo@latest </span><span class=\"token builtin class-name\">eval</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> promptfooconfig.yaml </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token operator\" style=\"color:#393A34\">&amp;&amp;</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">echo</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"✅ evaluación aprobada\"</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token operator\" style=\"color:#393A34\">||</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">echo</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"❌ evaluación fallida — bloqueando\"</span><span class=\"token punctuation\" style=\"color:#393A34\">;</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">exit</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">1</span><span class=\"token punctuation\" style=\"color:#393A34\">;</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Terminal: tabla de evaluación de puerta CI — mismos resultados de 4 modelos, 9 aprobados / 3 fallidos\" src=\"https://wec.wiline.com/docs/es/assets/images/promptfoo-ci-gate-table-7067b62a9b737ed79e3906b6da54ea2a.png\" width=\"968\" height=\"989\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 15a.</strong> La puerta vuelve a ejecutar la evaluación de matriz desde caché — mismo resultado 9/3.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Terminal: ❌ evaluación fallida — bloqueando\" src=\"https://wec.wiline.com/docs/es/assets/images/promptfoo-ci-gate-result-8765593b4f037c480e7fcef844ff6eb4.png\" width=\"959\" height=\"647\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 15b.</strong> Promptfoo sale no cero, se activa la rama <code>||</code>, y la puerta bloquea.</p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>tip</div><div class=\"admonitionContent_BuS1\"><p>Nunca ejecutes la variante <code>exit 1</code> en tu terminal interactiva — consulta <a href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/#3-exit-1-closes-your-ssh-session\" class=\"\">Solución de problemas #3</a>.</p></div></div>\n<p>En CI, ni siquiera necesitas el <code>exit 1</code> — la salida no cero falla el trabajo. En GitLab:</p>\n<div class=\"language-yaml codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-yaml codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token key atrule\" style=\"color:#00a4db\">eval</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">image</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"> node</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token number\" style=\"color:#36acaa\">22</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token key atrule\" style=\"color:#00a4db\">script</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\"> npx </span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">y promptfoo@latest eval </span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\">c promptfooconfig.yaml</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># WEC_API_KEY se establece como una variable CI/CD enmascarada — nunca la cometas.</span><br></div></code></pre></div></div>\n<p>Ahora un cambio de aviso que silenciosamente rompe la salida JSON, o un cambio de modelo que duplica la latencia, falla la canalización antes de que llegue a los usuarios.</p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Habilidad desbloqueada 🏅</div><div class=\"admonitionContent_BuS1\"><p>Tu evaluación es ahora una <strong>puerta de despliegue</strong> — las regresiones fallan CI en lugar de llegar a los usuarios.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"solución-de-problemas-errores-reales\">Solución de problemas (errores reales)<a href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/#soluci%C3%B3n-de-problemas-errores-reales\" class=\"hash-link\" aria-label=\"Enlace directo al Solución de problemas (errores reales)\" title=\"Enlace directo al Solución de problemas (errores reales)\" translate=\"no\">​</a></h2>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"1-error-de-bytestring-en-la-primera-evaluación\">1. Error de ByteString en la primera evaluación<a href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/#1-error-de-bytestring-en-la-primera-evaluaci%C3%B3n\" class=\"hash-link\" aria-label=\"Enlace directo al 1. Error de ByteString en la primera evaluación\" title=\"Enlace directo al 1. Error de ByteString en la primera evaluación\" translate=\"no\">​</a></h3>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Error de llamada a la API: TypeError: Cannot convert argument to a ByteString because</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">the character at index 32 has a value of 8232 which is greater than 255.</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Terminal: error de ByteString, ambos casos ERROR\" src=\"https://wec.wiline.com/docs/es/assets/images/promptfoo-invisible-char-error-540ad4b2ab9242c120ae80870319df01.png\" width=\"996\" height=\"627\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 2.</strong> Cada llamada a la API falla antes de llegar al servidor — la clave nunca siquiera sale de la máquina.</p>\n<p><strong>Causa:</strong> La clave pegada contiene un carácter Unicode invisible — U+2028 (separador de línea). Algunos terminales y administradores de portapapeles lo insertan silenciosamente al copiar de ciertas fuentes.</p>\n<p><strong>Solución:</strong> Elimina los caracteres no imprimibles de la clave:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token builtin class-name\">export</span><span class=\"token plain\"> </span><span class=\"token assign-left variable\" style=\"color:#36acaa\">WEC_API_KEY</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token variable\" style=\"color:#36acaa\">$(</span><span class=\"token variable builtin class-name\" style=\"color:#36acaa\">printf</span><span class=\"token variable\" style=\"color:#36acaa\"> </span><span class=\"token variable string\" style=\"color:#e3116c\">'%s'</span><span class=\"token variable\" style=\"color:#36acaa\"> </span><span class=\"token variable string\" style=\"color:#e3116c\">\"</span><span class=\"token variable string variable\" style=\"color:#36acaa\">$WEC_API_KEY</span><span class=\"token variable string\" style=\"color:#e3116c\">\"</span><span class=\"token variable\" style=\"color:#36acaa\"> </span><span class=\"token variable operator\" style=\"color:#393A34\">|</span><span class=\"token variable\" style=\"color:#36acaa\"> </span><span class=\"token variable assign-left variable environment constant\" style=\"color:#36acaa\">LC_ALL</span><span class=\"token variable operator\" style=\"color:#393A34\">=</span><span class=\"token variable\" style=\"color:#36acaa\">C </span><span class=\"token variable function\" style=\"color:#d73a49\">tr</span><span class=\"token variable\" style=\"color:#36acaa\"> </span><span class=\"token variable parameter variable\" style=\"color:#36acaa\">-cd</span><span class=\"token variable\" style=\"color:#36acaa\"> </span><span class=\"token variable string\" style=\"color:#e3116c\">'[:print:]'</span><span class=\"token variable\" style=\"color:#36acaa\">)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token builtin class-name\">printf</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'len=%s\\n'</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token string variable\" style=\"color:#36acaa\">$(</span><span class=\"token string variable builtin class-name\" style=\"color:#36acaa\">printf</span><span class=\"token string variable\" style=\"color:#36acaa\"> %s </span><span class=\"token string variable string\" style=\"color:#e3116c\">\"</span><span class=\"token string variable string variable\" style=\"color:#36acaa\">$WEC_API_KEY</span><span class=\"token string variable string\" style=\"color:#e3116c\">\"</span><span class=\"token string variable\" style=\"color:#36acaa\"> </span><span class=\"token string variable operator\" style=\"color:#393A34\">|</span><span class=\"token string variable\" style=\"color:#36acaa\"> </span><span class=\"token string variable function\" style=\"color:#d73a49\">wc</span><span class=\"token string variable\" style=\"color:#36acaa\"> </span><span class=\"token string variable parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token string variable\" style=\"color:#36acaa\">)</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># len=25</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Terminal: comando de eliminación de clave + confirmación de len=25\" src=\"https://wec.wiline.com/docs/es/assets/images/promptfoo-key-strip-1a9c78d8c230c69db3e89eecfdfa24ca.png\" width=\"712\" height=\"70\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 3.</strong> Después de eliminar, la longitud de la clave confirma que no quedan caracteres adicionales. Vuelve a ejecutar la evaluación — pasa.</p>\n<hr>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"2-errores-de-afirmación-de-costo-inmediatamente\">2. Errores de afirmación de costo inmediatamente<a href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/#2-errores-de-afirmaci%C3%B3n-de-costo-inmediatamente\" class=\"hash-link\" aria-label=\"Enlace directo al 2. Errores de afirmación de costo inmediatamente\" title=\"Enlace directo al 2. Errores de afirmación de costo inmediatamente\" translate=\"no\">​</a></h3>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Error: La afirmación de costo no admite proveedores que no devuelven costo</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Terminal: afirmación de costo, 2 errores\" src=\"https://wec.wiline.com/docs/es/assets/images/promptfoo-cost-error-1615b4a5c8b0973274d32c6d658a2e12.png\" width=\"953\" height=\"931\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 10.</strong> La API de Inferencia WEC no devuelve un campo de costo en sus respuestas.</p>\n<p><strong>Causa:</strong> Promptfoo calcula el costo buscando un precio por token para el modelo en su lista de precios incorporada y multiplicando por los tokens utilizados. Tiene precios para los grandes modelos alojados pero no para los nombres de modelos autoalojados de WEC, por lo que no tiene un precio que aplicar y la afirmación falla. (La mayoría de las API, incluida OpenAI, no devuelven un costo en dólares en la respuesta — las herramientas lo calculan).</p>\n<p><strong>Solución:</strong> Omite la afirmación de <code>cost</code> por completo. Realiza un seguimiento del gasto desde la línea <code>Total Tokens</code> que Promptfoo imprime al final de cada ejecución, y usa <code>max_tokens</code> en la configuración del proveedor para limitar el uso de tokens por llamada.</p>\n<hr>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"3-exit-1-cierra-tu-sesión-ssh\">3. <code>exit 1</code> cierra tu sesión SSH<a href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/#3-exit-1-cierra-tu-sesi%C3%B3n-ssh\" class=\"hash-link\" aria-label=\"Enlace directo al 3-exit-1-cierra-tu-sesión-ssh\" title=\"Enlace directo al 3-exit-1-cierra-tu-sesión-ssh\" translate=\"no\">​</a></h3>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">❌ evaluación fallida — bloqueando</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">logout</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Conexión a 10.80.4.212 cerrada.</span><br></div></code></pre></div></div>\n<p><strong>Causa:</strong> Ejecutar <code>|| { echo \"❌ evaluación fallida — bloqueando\"; exit 1; }</code> directamente en una terminal interactiva cierra el proceso de la terminal en sí — lo que, a través de SSH, cierra la conexión.</p>\n<p><strong>Solución:</strong> Coloca la puerta en un contexto no interactivo: un objetivo de <code>Makefile</code>, un script de <code>npm</code>, un gancho <code>pre-commit</code> de git, o un trabajo de CI. En esos entornos, <code>exit 1</code> falla el trabajo sin tocar tu terminal. Si necesitas probar la puerta localmente, usa <code>|| echo \"❌ evaluación fallida\"</code> (sin <code>exit 1</code>) para ver la salida de forma segura.</p>\n<hr>\n<div class=\"skillComplete\"><span class=\"skillComplete__medal\" aria-hidden=\"true\">🎯</span><div class=\"skillComplete__text\"><div class=\"skillComplete__title\">Finished this tutorial?</div><div class=\"skillComplete__sub\">Mark it complete to earn <strong>Prove a model works</strong> on your skill path.</div></div><button type=\"button\" class=\"skillComplete__btn\">Mark as complete ✓</button></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"qué-sigue\">¿Qué sigue?<a href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/#qu%C3%A9-sigue\" class=\"hash-link\" aria-label=\"Enlace directo al ¿Qué sigue?\" title=\"Enlace directo al ¿Qué sigue?\" translate=\"no\">​</a></h2>\n<p>Has pasado de \"se ve bien para mí\" a un verdadero arnés de evaluación automatizable en WEC — con verificaciones deterministas, líneas de defensa, validación de esquemas, rúbricas calificadas por modelos, una matriz de modelos, y una puerta CI.</p>\n<p>Próximamente en esta serie:</p>\n<ul>\n<li class=\"\"><strong><a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/structured-output-evals-wiline-inference/\">JSON confiable: valida el esquema de la salida estructurada de tu modelo</a></strong>\n— <strong>ahora disponible (parte 2):</strong> recupera la salida envuelta en cercas y razonamiento con transformaciones, compara modelos por confiabilidad JSON, y bloquea todo en CI.</li>\n<li class=\"\"><strong>Evaluaciones calificadas por modelos bien hechas</strong> — calibrando <code>llm-rubric</code>, puntuación de factualidad, y verificaciones de múltiples jueces para que tu calificador sea confiable.</li>\n<li class=\"\"><strong>Capturando alucinaciones</strong> — emparejando estas técnicas con la <a href=\"https://wec.wiline.com/resources/rag-hallucination-eval\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">evaluación de alucinaciones RAG</a> de WEC.</li>\n<li class=\"\"><strong>Evaluando audio</strong> — puntuando la precisión de la transcripción de Whisper en la API de Inferencia WEC.</li>\n</ul>",
            "url": "https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/",
            "title": "Evalúa tus modelos con Promptfoo en la API de Inferencia WEC",
            "summary": "Deja de estimar la salida de LLM. Construye un verdadero arnés de evaluación con Promptfoo apuntando a la API de Inferencia WEC — afirmaciones, límites de latencia, verificaciones de esquema JSON, rúbricas calificadas por el modelo, una comparación de modelos totalmente WEC, y una puerta CI. Cada comando y resultado es real.",
            "date_modified": "2026-06-30T00:00:00.000Z",
            "author": {
                "name": "Rafael Fernandes",
                "url": "https://www.linkedin.com/in/rafaelmacariofernandes/"
            },
            "tags": [
                "ai",
                "evals",
                "promptfoo",
                "inference",
                "testing",
                "observability"
            ]
        },
        {
            "id": "https://wec.wiline.com/docs/es/tutorials/run-openclaw-on-wec-models/",
            "content_html": "<div class=\"skillTracker\" tabindex=\"0\" aria-label=\"Skill path: 0 of 6 skills earned\"><div class=\"skillTracker__ring\" style=\"--skillTracker-pct:0%\"><span class=\"skillTracker__ringLabel\">0<!-- -->/<!-- -->6</span></div><div class=\"skillTracker__panel\"><div class=\"skillTracker__header\"><span class=\"skillTracker__title\">🎯 Skill path</span><span class=\"skillTracker__count\">0<!-- -->/<!-- -->6<!-- --> earned</span></div><div class=\"skillTracker__series\">Self-hosting OpenClaw</div><ul class=\"skillTracker__steps\"><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/\"><span class=\"skillTracker__dot\" data-state=\"locked\">1</span><span class=\"skillTracker__skill\" data-state=\"locked\">Deploy your own AI assistant</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/\"><span class=\"skillTracker__dot\" data-state=\"locked\">2</span><span class=\"skillTracker__skill\" data-state=\"locked\">Real HTTPS + auth</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/add-telegram-channel-openclaw/\"><span class=\"skillTracker__dot\" data-state=\"locked\">3</span><span class=\"skillTracker__skill\" data-state=\"locked\">Chat from Telegram</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/\"><span class=\"skillTracker__dot\" data-state=\"locked\">4</span><span class=\"skillTracker__skill\" data-state=\"locked\">Private mesh access</span></a></li><li><span class=\"skillTracker__step\"><span class=\"skillTracker__dot\" data-state=\"current\">5</span><span class=\"skillTracker__skill\" data-state=\"current\">Run it on WEC models</span></span></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/add-whatsapp-channel-openclaw/\"><span class=\"skillTracker__dot\" data-state=\"locked\">🏆</span><span class=\"skillTracker__skill\" data-state=\"locked\">Chat from WhatsApp</span></a></li></ul><div class=\"skillTracker__footer\">Finish this tutorial, then mark it complete below to earn the skill.</div></div></div>\n<div class=\"tutorialHero\"><img class=\"tutorialHero__brand\" src=\"https://wec.wiline.com/docs/es/assets/images/openclaw-wordmark-003352a1a7f02afc3bd877ae6f2dc175.png\" alt=\"OpenClaw\"><span class=\"tutorialHero__plus\">+</span><svg xmlns=\"http://www.w3.org/2000/svg\" width=\"242\" height=\"255\" fill=\"none\" viewBox=\"0 0 242 255\" class=\"tutorialHero__wiline\"><path fill=\"#4E8EF7\" d=\"M56.711 11.293A195 195 0 0 1 41.899 4.79c4.788 1.85 9.71 4.028 14.812 6.504M73.807 29.35c-1.302-1.446-2.666-3.233-4.02-5.38 1.326 1.44 2.642 3.24 4.02 5.38M69.761 23.354c-.832-.585-1.752-1.472-2.558-2.714.9.567 1.685 1.49 2.558 2.714M62.97 15.774a10.7 10.7 0 0 1-3.158-1.841c.993.299 1.986.956 3.159 1.841M59.715 13.346c-.818-.194-1.782-.674-2.829-1.494.84.175 1.761.691 2.83 1.494M39.317 3.782c-.524.192-1.413.302-2.455.14.594-.255 1.342-.238 2.455-.14M74.906 31.339c-.334-.153-.73-.61-1.117-1.398.358.143.706.619 1.117 1.398M66.024 18.878c-.239.1-.557.05-.752-.276.35-.201.532-.069.752.276M71.773 150.625c3.363 7.291 7.185 14.411 9.983 21.914 4.474 11.997 11.167 20.832 24.296 24.757 10.62 3.175 20.369 1.795 30.093-1.816 9.937-3.689 15.165-11.978 18.501-21.541 4.349-12.466 9.385-24.443 19.668-33.446 11.32-9.909 24.639-14.788 39.429-15.63 8.3-.473 16.649-.081 25.444.444.472 15.673.385 30.825.55 45.974.038 3.499-1.049 4.719-4.557 4.543-4.982-.25-9.987-.079-14.98.009-.209.004-.4.991-.6 1.521-23.106 1.695-37.691 15.22-47.961 34.651-3.387 6.408-5.913 13.38-10.013 19.273-4.368 6.277-10.134 11.582-15.853 17.196-6.439 1.421-12.244 3.342-18.202 4.416-12.627 2.274-24.661.457-35.764-6.356-1.028-.631-2.395-.709-3.604-1.043 0 0-.117-.11-.047-.495-.818-.973-1.706-1.562-2.593-2.15 0 0 .09-.044.05-.335-.513-.545-.988-.798-1.463-1.052 0 0-.07-.123.012-.492-.561-.894-1.205-1.421-1.848-1.947 0 0 .134-.068.102-.28-.321-.442-.61-.671-.898-.901 0 0 .086-.007.074-.336-2.05-3.429-4.089-6.529-6.127-9.629 0 0 .094-.017.123-.365-3.013-6.117-6.056-11.886-9.1-17.655 0 0 .096-.01.088-.342-1.978-4.073-3.89-7.848-5.997-11.51-.246-.428-1.572-.235-2.398-.329 0 0-.226-.202-.114-.613-.213-.499-.537-.587-.861-.674 0 0-.032-.101.02-.455-.62-.846-1.291-1.339-1.964-1.832 0 0 .133-.083.119-.372-2.476-4.624-5.645-8.048-10.914-8.777 0 0 .108-.031.01-.326-1.673-1.444-3.105-2.918-4.85-3.688-9.751-4.308-20.013-5.643-30.63-5.23-7.305.285-7.316.051-7.317-7.446q-.002-18.496-.002-36.992v-3.57h36.106s-.08.019.133.283c1.43.415 2.646.567 3.863.719 0 0-.077.022.113.296 1.804.591 3.417.908 5.03 1.225 0 0 .139-.037.152.369 1.592.962 3.17 1.517 4.748 2.073 0 0-.091.027.052.267.735.415 1.328.59 1.92.765 0 0-.076.033.053.246.477.325.825.438 1.173.551 0 0-.149.018-.07.246.418.239.757.251 1.096.263 0 0 .23.058.189.465 1.522 1.319 3.084 2.231 4.647 3.143 0 0-.105.033-.015.316 2.723 2.764 5.356 5.244 7.989 7.724 0 0-.104.062-.094.352 1.01 1.401 2.01 2.512 3.01 3.624\"></path><path fill=\"#4E8EF7\" d=\"M214.77 52.819c-3.343.846-6.723 1.574-10.024 2.562-11.388 3.408-20.567 10.142-27.166 19.865-5.097 7.511-9.621 15.483-13.685 23.608-6.809 13.613-15.725 24.809-31.343 28.198-24.707 5.362-45.22-.126-57.852-24.564C69.466 92.363 63.8 82.375 57.349 73c-3.369-4.896-8.866-8.326-13.679-13.041-1.11-1.409-1.81-2.472-2.794-2.937-10.697-5.056-21.87-7.3-33.686-5.344-3.877.641-5.72-.297-5.615-4.86.294-12.816.26-25.646.019-38.464C1.509 3.874 3.15 2.5 7.429 2.716c9.774.492 19.564.678 29.347.989 0 0-.076.026.082.257 1.046.122 1.935.012 2.825-.098.575.043 1.15.086 2.038.692 5.265 2.72 10.218 4.877 15.17 7.034 0 0-.108.03-.016.317 1.058.765 2.022 1.245 2.987 1.724 0 0-.113.048-.082.354 1.144.876 2.258 1.446 3.372 2.017 0 0 .18.145.097.506.223.404.53.447.837.49 0 0 .214.13.144.504.244.426.558.477.873.528 0 0 .171.124.108.485.256.412.574.464.893.515 0 0 .167.127.105.493.265.437.592.509.919.58 0 0 .078.108.019.5.861 1.28 1.782 2.166 2.702 3.053 0 0-.101.061-.087.368 1.379 2.093 2.743 3.88 4.107 5.666 0 0-.094.011-.092.297.4.742.795 1.2 1.19 1.656 0 0-.121.02-.165.337.685 1.432 1.413 2.549 2.14 3.665 0 0-.11.024-.129.299.326.776.67 1.277 1.014 1.78 0 0-.084-.005-.1.367.933 4.079.311 8.497 4.648 10.67 4.69 12.164 12.107 21.794 25.852 23.497 19.618 2.431 35.321.062 45.299-24.554 3.398-8.383 7.099-16.902 12.232-24.264 8.353-11.979 21.046-17.795 35.832-20.32 1.666.417 2.649.651 3.624.622 9.93-.298 19.865-.513 29.785-1.032 3.818-.2 4.825 1.198 4.746 4.853-.218 9.967.004 19.944-.109 29.915-.052 4.583 1.149 10.606-1.239 13.302-2.044 2.308-8.394.86-12.842.967-3.82.092-7.644.036-11.467.045.065.31.097.633.213.922.034.084.306.072.469.105M37.585 127.354c-11.585.344-23.368.344-35.907.344v3.57q0 18.496.002 36.992c0 7.497.012 7.731 7.318 7.446 10.616-.413 20.878.922 30.63 5.23 1.743.77 3.176 2.244 4.84 3.744-13.655-7.873-28.93-7.767-44.468-7.398v-52.503c12.516.308 25.063-1.222 37.585 2.575\"></path><path fill=\"#4E8EF7\" d=\"M36.619 3.401c-9.626-.007-19.415-.193-29.19-.685C3.151 2.5 1.51 3.874 1.594 8.354c.241 12.818.275 25.648-.018 38.464-.105 4.563 1.737 5.501 5.614 4.86 11.816-1.956 22.99.288 33.686 5.344.984.465 1.685 1.529 2.616 2.66-13.39-7.538-28.284-7.343-43.479-6.78V0C12.314 1.045 24.388 2.071 36.62 3.401M215.232 52.852c-.625-.066-.897-.054-.931-.138-.116-.29-.148-.612-.212-.922 3.822-.01 7.646.047 11.467-.045 4.448-.108 10.797 1.341 12.842-.967 2.387-2.696 1.186-8.719 1.238-13.302.114-9.971-.109-19.948.109-29.915.08-3.655-.928-5.052-4.746-4.853-9.919.519-19.855.734-29.785 1.032-.975.03-1.958-.205-3.228-.58C214.559 1.945 227.423.991 240.73.003v52.882c-8.276 0-16.656 0-25.498-.033M220.071 177.398c-.271-.573-.08-1.561.129-1.565 4.993-.088 9.998-.259 14.98-.009 3.508.176 4.595-1.044 4.557-4.543-.165-15.149-.078-30.301-.088-45.911.458.372 1.32 1.202 1.324 2.036.079 16.424.059 32.848.059 50.036-6.984 0-13.737 0-20.961-.044M88.39 245.741c1.022.084 2.39.162 3.417.793 11.102 6.813 23.137 8.63 35.763 6.356 5.959-1.073 11.764-2.995 17.871-4.34-5.612 2.132-11.343 5.166-17.325 5.79-13.898 1.451-27.803 1.213-39.725-8.599M66.556 210.212c2.975 5.411 6.018 11.18 9.02 17.344-3.011-5.4-5.982-11.193-9.02-17.344M58.341 197.934c.666-.167 1.992-.36 2.238.068 2.107 3.662 4.018 7.437 5.985 11.557-2.69-3.535-5.376-7.449-8.223-11.625M44.637 185.241c5.099.438 8.268 3.862 10.737 8.564-3.518-2.513-7.043-5.393-10.737-8.564M82.29 48.422c-4.252-1.834-3.63-6.252-4.548-10.37 1.508 3.069 2.986 6.55 4.548 10.37M68.757 146.326c-2.533-2.157-5.166-4.637-7.882-7.455 2.539 2.151 5.16 4.641 7.882 7.455M75.532 228.226a122 122 0 0 1 6.047 9.319c-1.993-2.742-3.986-5.854-6.047-9.319M71.695 150.312c-.922-.799-1.921-1.91-2.906-3.366.952.788 1.89 1.921 2.906 3.366M46.663 130.007c-1.353-.103-2.966-.42-4.767-1.051 1.377.07 2.941.454 4.767 1.051M51.647 132.369a13.1 13.1 0 0 1-4.501-1.77c1.48.227 2.902.852 4.5 1.77M60.724 138.321a17.7 17.7 0 0 1-4.395-2.781c1.501.609 2.869 1.551 4.395 2.781M76.889 35.31c-.674-.781-1.402-1.898-2.068-3.39.711.768 1.362 1.912 2.068 3.39M85.668 243.124c.782.31 1.67.899 2.423 1.825-.862-.29-1.59-.919-2.423-1.825M41.596 128.372c-1.033.176-2.25.024-3.68-.432 1.023-.169 2.26-.032 3.68.432M55.367 194.37c.568.222 1.24.715 1.808 1.548-.637-.199-1.17-.738-1.808-1.548M82.397 239.295c.56.251 1.204.778 1.71 1.628-.633-.235-1.13-.794-1.71-1.628M84.291 241.679c.334.034.809.287 1.298.875-.376.004-.766-.326-1.298-.875M53.64 133.426c-.438.094-1.03-.081-1.764-.543.441-.101 1.026.086 1.764.543M77.805 37.409c-.322-.188-.666-.69-.978-1.52.34.182.648.693.978 1.52M81.588 238.108c.218-.039.507.19.823.707-.233.045-.493-.196-.823-.707M55.906 134.832c-.198.157-.537.145-.92-.161.262-.29.537-.195.92.161M57.276 196.552c.254-.079.578.009.756.361-.377.171-.557.018-.756-.361M54.888 134.25c-.215.129-.563.017-1.038-.351.217-.133.56-.012 1.038.351M64.014 16.858a.545.545 0 0 1-.71-.247c.344-.19.51-.067.71.247M65.027 17.882a.574.574 0 0 1-.734-.272c.35-.193.525-.063.734.272M67.045 19.939c-.244.093-.57.021-.777-.329.358-.192.55-.044.777.329\"></path></svg></div>\n<p>A través de <a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/\">Partes 1–4</a> implementaste\nOpenClaw, lo aseguraste con HTTPS, añadiste Telegram y lo hiciste privado a través de una\nmalla NetBird — todo apuntando a <strong>OpenAI</strong>. Esta parte intercambia el modelo por debajo:\napunta el mismo agente a <strong>Modelos WEC</strong> — la inferencia compatible con OpenAI de WiLine —\ny ejecútalo en un modelo de peso abierto, <strong>Llama 3.1 8B Instruct</strong>. Misma caja, sin reconstrucción\n— solo un cambio de base-URL, clave y modelo a través de la CLI de OpenClaw.</p>\n<!-- -->\n<div class=\"theme-admonition theme-admonition-info admonition_xJq3 alert alert--info\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z\"></path></svg></span>Reproducibilidad</div><div class=\"admonitionContent_BuS1\"><p>Continúa en la <strong>misma Instancia WEC</strong> de las Partes 1–4. Modelos WEC es un\npunto final <strong>compatible con OpenAI</strong> en <code>https://inference.wiline.com/v1</code>; cada comando\na continuación se ejecutó contra un contenedor de puerta de enlace OpenClaw en vivo\n(<code>openclaw-openclaw-gateway-1</code>).</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"por-qué-apuntar-openclaw-a-modelos-wec\">Por qué apuntar OpenClaw a Modelos WEC<a href=\"https://wec.wiline.com/docs/es/tutorials/run-openclaw-on-wec-models/#por-qu%C3%A9-apuntar-openclaw-a-modelos-wec\" class=\"hash-link\" aria-label=\"Enlace directo al Por qué apuntar OpenClaw a Modelos WEC\" title=\"Enlace directo al Por qué apuntar OpenClaw a Modelos WEC\" translate=\"no\">​</a></h2>\n<p>Si autoalojaste el agente para empezar, ya te importa el control — no\ndepender de la nube de otra persona, no estar a un correo de la página de precios de\nreconstrucción. Pero hay una brecha en esa historia: el agente se ejecuta en tu caja, y cada\nmensaje que maneja todavía se envía a OpenAI para que lo procese. Autoalojar\nel agente y externalizar el modelo es solo la mitad del trabajo. Esta parte lo cierra.</p>\n<ul>\n<li class=\"\"><strong>Un modelo, de extremo a extremo en WiLine</strong> — tu agente <em>y</em> su modelo se ejecutan en WEC; sin\nproveedor de terceros en la ruta caliente.</li>\n<li class=\"\"><strong>Pesos abiertos</strong> — Llama 3.1 no es una caja cerrada; no estás bloqueado a una sola\nAPI de proveedor para mantener el agente en funcionamiento.</li>\n<li class=\"\"><strong>Es un cambio de configuración, no una migración</strong> — OpenClaw ya habla la API de OpenAI,\ny Modelos WEC es compatible con OpenAI, por lo que mantienes tu agente, prompts y\ncanales intactos.</li>\n</ul>\n<p>También es más barato. El catálogo propio de WEC fija el precio de <code>Llama3.1-8B-Instruct</code> en\n<strong>$0.15 / 1M tokens de entrada · $0.20 / 1M tokens de salida</strong>. OpenAI no publica una\ntarifa independiente para <code>gpt-5.5</code> (el modelo que OpenClaw utiliza por defecto en\n<a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/\">Parte 1</a>) más, pero su miembro familiar más cercano\nactual — GPT-5.6 \"Terra\", presentado por OpenAI como\n<a class=\"\" href=\"https://wec.wiline.com/docs/es/news/gpt-5-6-token-economics/\">\"competitivo en rendimiento con GPT-5.5\"</a> — se lista en\n<strong>$2.50 / 1M en · $15 / 1M fuera</strong>. Esa es una comparación aproximada, no un\nbenchmark de manzanas a manzanas — el movimiento honesto, como siempre en este blog, es\n<a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/\">evalúalo en tu propia carga de trabajo</a>\nen lugar de confiar en el número de ningún proveedor. Pero la dirección es lo suficientemente clara como para valer los diez minutos que esto toma.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"requisitos-previos\">Requisitos previos<a href=\"https://wec.wiline.com/docs/es/tutorials/run-openclaw-on-wec-models/#requisitos-previos\" class=\"hash-link\" aria-label=\"Enlace directo al Requisitos previos\" title=\"Enlace directo al Requisitos previos\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\">OpenClaw ejecutándose desde <a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/\">Partes 1–4</a> (cualquiera\nde las configuraciones funciona). Esta guía asume que el contenedor de puerta de enlace se llama\n<code>openclaw-openclaw-gateway-1</code>.</li>\n<li class=\"\">Una <strong>clave API de WEC</strong>.</li>\n<li class=\"\">Acceso a la terminal de la caja de OpenClaw.</li>\n</ul>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-1--obtener-una-clave-api-de-wec\">Paso 1 — Obtener una clave API de WEC<a href=\"https://wec.wiline.com/docs/es/tutorials/run-openclaw-on-wec-models/#paso-1--obtener-una-clave-api-de-wec\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 1 — Obtener una clave API de WEC\" title=\"Enlace directo al Paso 1 — Obtener una clave API de WEC\" translate=\"no\">​</a></h2>\n<p>Consulta <a class=\"\" href=\"https://wec.wiline.com/docs/es/cloud_portal/platform/inference/api_keys/\">Inferencia → Claves API</a> si no\ntienes una (la misma clave de\n<a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/\">Parte 1 de la serie de evaluaciones</a>\nfunciona bien aquí).</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"La página de Claves API del portal de WiLine\" src=\"https://wec.wiline.com/docs/es/assets/images/promptfoo-api-key-a1aca6bfffa7335651ab59ec1c07d8f4.png\" width=\"1642\" height=\"866\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 1.</strong> Crea una clave en Inferencia → Claves API, luego cópiala.</p>\n<p>Mantenla fuera de tu historial de terminal — ponla en un archivo en lugar de exportarla\ndirectamente:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token builtin class-name\">echo</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'WEC_API_KEY=sk-wec-...'</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;&gt;</span><span class=\"token plain\"> env.local</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token builtin class-name\">source</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&lt;</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token function\" style=\"color:#d73a49\">grep</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-v</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'^#'</span><span class=\"token plain\"> env.local </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">sed</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'s/^/export /'</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><br></div></code></pre></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-2--encontrar-el-id-del-modelo\">Paso 2 — Encontrar el ID del modelo<a href=\"https://wec.wiline.com/docs/es/tutorials/run-openclaw-on-wec-models/#paso-2--encontrar-el-id-del-modelo\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 2 — Encontrar el ID del modelo\" title=\"Enlace directo al Paso 2 — Encontrar el ID del modelo\" translate=\"no\">​</a></h2>\n<p>Consulta el punto final compatible con OpenAI <code>/v1/models</code> para ver qué está activo en tu Instancia WEC:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-s</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-H</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Authorization: Bearer </span><span class=\"token string variable\" style=\"color:#36acaa\">$WEC_API_KEY</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"> https://inference.wiline.com/v1/models</span><br></div></code></pre></div></div>\n<p>La respuesta lista cada modelo disponible para tu clave, junto con el id que\nutiliza este tutorial:</p>\n<div class=\"language-json codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-json codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token property\" style=\"color:#36acaa\">\"data\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  ...</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token property\" style=\"color:#36acaa\">\"id\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token string\" style=\"color:#e3116c\">\"Llama3.1-8B-Instruct\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token property\" style=\"color:#36acaa\">\"object\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token string\" style=\"color:#e3116c\">\"model\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token property\" style=\"color:#36acaa\">\"created\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token number\" style=\"color:#36acaa\">1677610602</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token property\" style=\"color:#36acaa\">\"owned_by\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token string\" style=\"color:#e3116c\">\"openai\"</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  ...</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><br></div></code></pre></div></div>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>tip</div><div class=\"admonitionContent_BuS1\"><p>Los ids de modelo en WEC no siempre son el nombre de visualización mostrado en el portal — siempre toma\nel <code>id</code> exacto de esta respuesta, no una suposición.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-3--apuntar-openclaw-a-modelos-wec\">Paso 3 — Apuntar OpenClaw a Modelos WEC<a href=\"https://wec.wiline.com/docs/es/tutorials/run-openclaw-on-wec-models/#paso-3--apuntar-openclaw-a-modelos-wec\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 3 — Apuntar OpenClaw a Modelos WEC\" title=\"Enlace directo al Paso 3 — Apuntar OpenClaw a Modelos WEC\" translate=\"no\">​</a></h2>\n<p>No edites manualmente <code>openclaw.json</code> — usa <code>openclaw config set</code>. Valida el\narchivo completo en cada escritura, por lo que un error tipográfico falla ruidosamente en lugar de romper\nsilenciosamente la puerta de enlace. Un proveedor personalizado necesita su base URL, su estilo de API y su lista de modelos declarados <strong>juntos</strong>, en una sola escritura — el esquema rechaza un proveedor que\nfalta alguno de los tres, así que construye el objeto completo desde el principio:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">exec</span><span class=\"token plain\"> openclaw-openclaw-gateway-1 openclaw config </span><span class=\"token builtin class-name\">set</span><span class=\"token plain\"> models.providers.wec </span><span class=\"token string\" style=\"color:#e3116c\">'{</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  \"baseUrl\": \"https://inference.wiline.com/v1\",</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  \"api\": \"openai-completions\",</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  \"models\": [</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    {\"id\": \"Llama3.1-8B-Instruct\", \"name\": \"Llama 3.1 8B Instruct\"}</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  ]</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">}'</span><span class=\"token plain\"> --strict-json</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Updated models.providers.wec. Change will apply without restarting the gateway.</span><br></div></code></pre></div></div>\n<p>Eso registra WEC como un proveedor, pero aún no está autenticado. No pongas la\nclave directamente en ese JSON — la configuración de composición de OpenClaw mantiene las claves API fuera de\nlas variables de entorno por completo y las almacena en un directorio de secretos dedicado respaldado por archivos en su lugar (<code>OPENCLAW_AUTH_PROFILE_SECRET_DIR</code>, montado en el\ncontenedor). Pasa la clave al propio almacén de autenticación de OpenClaw y deja que haga la conexión:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token builtin class-name\">echo</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token string variable\" style=\"color:#36acaa\">$WEC_API_KEY</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">exec</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-i</span><span class=\"token plain\"> openclaw-openclaw-gateway-1 openclaw models auth paste-api-key </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--provider</span><span class=\"token plain\"> wec</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Updated config: $OPENCLAW_HOME/.openclaw/openclaw.json</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  Backup: $OPENCLAW_HOME/.openclaw/openclaw.json.bak</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Auth profile: wec:manual (wec/api_key)</span><br></div></code></pre></div></div>\n<p>Verifica que el proveedor y su autenticación se resolvieron juntos, luego haz que el modelo\nsea el predeterminado para que cada agente lo recoja sin configuración adicional:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">exec</span><span class=\"token plain\"> openclaw-openclaw-gateway-1 openclaw models list</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">exec</span><span class=\"token plain\"> openclaw-openclaw-gateway-1 openclaw models </span><span class=\"token builtin class-name\">set</span><span class=\"token plain\"> wec/Llama3.1-8B-Instruct</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Modelo                                      Entrada      Ctx         Autenticación Local  Etiquetas</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">wec/Llama3.1-8B-Instruct                   texto       200k        no    sí   configurado</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">openai/gpt-5.5                             texto       200k        no    sí   configurado,alias:GPT</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">...</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Updated config: $OPENCLAW_HOME/.openclaw/openclaw.json</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  Backup: $OPENCLAW_HOME/.openclaw/openclaw.json.bak</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Modelo predeterminado: wec/Llama3.1-8B-Instruct</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Terminal mostrando la lista de modelos de openclaw con wec/Llama3.1-8B-Instruct configurado y establecido como modelo predeterminado\" src=\"https://wec.wiline.com/docs/es/assets/images/openclaw-wec-models-list-bb96e6c9a1dfe7d0c98be4a6b5b40ae6.png\" width=\"682\" height=\"449\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 2.</strong> <code>wec/Llama3.1-8B-Instruct</code>, autenticado y establecido como el modelo predeterminado.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-4--verificar\">Paso 4 — Verificar<a href=\"https://wec.wiline.com/docs/es/tutorials/run-openclaw-on-wec-models/#paso-4--verificar\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 4 — Verificar\" title=\"Enlace directo al Paso 4 — Verificar\" translate=\"no\">​</a></h2>\n<p>OpenClaw puede ejecutar más de un agente por puerta de enlace, así que cada mensaje necesita un\nobjetivo — pasa <code>--agent</code>, coincidiendo con lo que <code>openclaw agents list</code> llama tu\nagente (<code>main</code>, a menos que lo hayas renombrado):</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">exec</span><span class=\"token plain\"> openclaw-openclaw-gateway-1 openclaw agents list</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Agentes:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">- main (predeterminado)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  Espacio de trabajo: $OPENCLAW_HOME/.openclaw/workspace</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  Modelo: wec/Llama3.1-8B-Instruct</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  Reglas de enrutamiento: 0</span><br></div></code></pre></div></div>\n<p>Envíale un mensaje y ve quién responde:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">exec</span><span class=\"token plain\"> openclaw-openclaw-gateway-1 openclaw agent </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--agent</span><span class=\"token plain\"> main </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--message</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"¿Cuál es la capital de Francia?\"</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">París es la capital de Francia.</span><br></div></code></pre></div></div>\n<p>Eso es WEC, en Llama 3.1, sin OpenAI en el circuito.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Terminal mostrando el agente respondiendo &amp;quot;París es la capital de Francia.&amp;quot; después de cambiar a WEC\" src=\"https://wec.wiline.com/docs/es/assets/images/openclaw-wec-agent-verified-e5125e1d417f258ba73f9d11b78244ff.png\" width=\"675\" height=\"50\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span>\n<strong>Figura 3.</strong> El agente, ejecutándose en Llama 3.1 8B Instruct a través de WEC.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-5-opcional--desactivar-el-modo-de-pensamiento\">Paso 5 (opcional) — Desactivar el modo de pensamiento<a href=\"https://wec.wiline.com/docs/es/tutorials/run-openclaw-on-wec-models/#paso-5-opcional--desactivar-el-modo-de-pensamiento\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 5 (opcional) — Desactivar el modo de pensamiento\" title=\"Enlace directo al Paso 5 (opcional) — Desactivar el modo de pensamiento\" translate=\"no\">​</a></h2>\n<p>¿Quieres que sea más rápido? Desactiva el modo de pensamiento — es un comando de barra dentro del\nchat, no un comando de terminal:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">exec</span><span class=\"token plain\"> openclaw-openclaw-gateway-1 openclaw agent </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--agent</span><span class=\"token plain\"> main </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--message</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"/think off\"</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">El modo de pensamiento ha sido desactivado.</span><br></div></code></pre></div></div>\n<p>O haz que se mantenga para cada sesión:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">exec</span><span class=\"token plain\"> openclaw-openclaw-gateway-1 openclaw config </span><span class=\"token builtin class-name\">set</span><span class=\"token plain\"> agents.defaults.thinkingDefault </span><span class=\"token string\" style=\"color:#e3116c\">\"off\"</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Updated agents.defaults.thinkingDefault. No se necesita reiniciar la puerta de enlace.</span><br></div></code></pre></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"solución-de-problemas-real\">Solución de problemas (real)<a href=\"https://wec.wiline.com/docs/es/tutorials/run-openclaw-on-wec-models/#soluci%C3%B3n-de-problemas-real\" class=\"hash-link\" aria-label=\"Enlace directo al Solución de problemas (real)\" title=\"Enlace directo al Solución de problemas (real)\" translate=\"no\">​</a></h2>\n<p><strong>\"los proveedores de modelos personalizados deben declarar modelos\"</strong> — declara <code>baseUrl</code>, <code>api</code>,\ny <code>models</code> juntos, en una sola llamada <code>config set</code>. Un proveedor personalizado que\nfalta alguno de los tres es rechazado; no puedes añadir <code>models</code> en una escritura posterior.</p>\n<p><strong>\"models.providers.wec.apiKey no está resuelto en la instantánea de tiempo de ejecución activa\"</strong>\n— no establezcas <code>apiKey</code> en el objeto del proveedor; deja que <code>models auth paste-api-key</code>\nlo posea (Paso 3). ¿Ya estableciste uno? Elimínalo:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">exec</span><span class=\"token plain\"> openclaw-openclaw-gateway-1 openclaw config </span><span class=\"token builtin class-name\">unset</span><span class=\"token plain\"> models.providers.wec.apiKey</span><br></div></code></pre></div></div>\n<p><strong>\"No se seleccionó ninguna sesión de destino\"</strong> — siempre pasa <code>--agent &lt;id&gt;</code> con\n<code>openclaw agent</code>. Verifica el id con <code>openclaw agents list</code> primero si no estás\nseguro (<code>main</code>, a menos que lo hayas renombrado).</p>\n<p><strong><code>-bash: /think: No such file or directory</code></strong> — <code>/think</code> es un comando de barra\npara dentro del chat, no un comando de terminal. Pásalo a través de <code>--message</code> en su lugar,\ncomo en el Paso 5.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"qué-sigue\">Qué sigue<a href=\"https://wec.wiline.com/docs/es/tutorials/run-openclaw-on-wec-models/#qu%C3%A9-sigue\" class=\"hash-link\" aria-label=\"Enlace directo al Qué sigue\" title=\"Enlace directo al Qué sigue\" translate=\"no\">​</a></h2>\n<p>Con el agente ejecutándose en nuestro propio modelo, el siguiente paso es <strong>medirlo</strong> —\n<a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/eval-models-promptfoo-wiline-inference/\">evaluando la calidad de las respuestas con Promptfoo + Modelos WEC</a>.</p>\n<div class=\"skillComplete\"><span class=\"skillComplete__medal\" aria-hidden=\"true\">🎯</span><div class=\"skillComplete__text\"><div class=\"skillComplete__title\">Finished this tutorial?</div><div class=\"skillComplete__sub\">Mark it complete to earn <strong>Run it on WEC models</strong> on your skill path.</div></div><button type=\"button\" class=\"skillComplete__btn\">Mark as complete ✓</button></div>",
            "url": "https://wec.wiline.com/docs/es/tutorials/run-openclaw-on-wec-models/",
            "title": "Ejecutar OpenClaw en Modelos WEC",
            "summary": "Intercambia el proveedor cerrado por la propia inferencia de WiLine: apunta tu agente OpenClaw autoalojado a Modelos WEC — misma caja, un cambio de base-URL/clave/modelo, sin bloqueo por token.",
            "date_modified": "2026-06-25T00:00:00.000Z",
            "author": {
                "name": "Rafael Fernandes",
                "url": "https://www.linkedin.com/in/rafaelmacariofernandes/"
            },
            "tags": [
                "ai",
                "autoalojamiento",
                "openclaw",
                "modelos-wec",
                "inferencia"
            ]
        },
        {
            "id": "https://wec.wiline.com/docs/es/tutorials/self-host-hermes-agent/",
            "content_html": "<div class=\"tutorialHero\"><img class=\"tutorialHero__brand\" src=\"https://wec.wiline.com/docs/es/assets/images/hermesagent-c76bbe81610ae2e70b0950e0f96bec8a.png\" alt=\"Hermes Agent\"><span class=\"tutorialHero__plus\">+</span><img class=\"tutorialHero__brand\" src=\"https://wec.wiline.com/docs/es/assets/images/SQLite370-e02c30e84f9e098787bb57701b0b7232.png\" alt=\"SQLite\"></div>\n<div class=\"skillTracker\" tabindex=\"0\" aria-label=\"Skill path: 0 of 2 skills earned\"><div class=\"skillTracker__ring\" style=\"--skillTracker-pct:0%\"><span class=\"skillTracker__ringLabel\">0<!-- -->/<!-- -->2</span></div><div class=\"skillTracker__panel\"><div class=\"skillTracker__header\"><span class=\"skillTracker__title\">🎯 Skill path</span><span class=\"skillTracker__count\">0<!-- -->/<!-- -->2<!-- --> earned</span></div><div class=\"skillTracker__series\">Self-hosting Hermes</div><ul class=\"skillTracker__steps\"><li><span class=\"skillTracker__step\"><span class=\"skillTracker__dot\" data-state=\"current\">1</span><span class=\"skillTracker__skill\" data-state=\"current\">Agent with persistent memory</span></span></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/telegram-on-hermes/\"><span class=\"skillTracker__dot\" data-state=\"locked\">🏆</span><span class=\"skillTracker__skill\" data-state=\"locked\">Telegram on Hermes</span></a></li></ul><div class=\"skillTracker__footer\">Finish this tutorial, then mark it complete below to earn the skill.</div></div></div>\n<p><a href=\"https://hermes-agent.nousresearch.com/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Hermes Agent</a> es el agente de IA de código abierto\n(MIT) de Nous Research — \"el agente que crece contigo.\" Su característica destacada es\n<strong>memoria persistente</strong>: aprende sobre tus proyectos y <strong>no olvida entre reinicios</strong>. Esta guía lo despliega en la <strong>misma Instancia WEC</strong> que ya usas para\nOpenClaw, lo apunta a un modelo y prueba que la memoria sobrevive a un reinicio completo.</p>\n<!-- -->\n<div class=\"theme-admonition theme-admonition-info admonition_xJq3 alert alert--info\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z\"></path></svg></span>Reproducibilidad</div><div class=\"admonitionContent_BuS1\"><p>Funciona en la <strong>misma Instancia WEC</strong> de la serie OpenClaw — sin nueva VM. El instalador\ndescarga su propio Python (a través de <code>uv</code>), herramientas de navegador Node.js y un Chromium\nde Playwright automáticamente. El estado se guarda en <code>~/.hermes/</code>. La puerta de enlace de mensajería se ejecuta como un <strong>servicio de usuario systemd</strong>, por lo que coexiste con la pila Docker de OpenClaw. Capturado en vivo en Ubuntu\n22.04 con <code>hermes</code> <strong>v0.17.0</strong>.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"por-qué-importa-la-memoria-persistente\">Por qué importa la memoria persistente<a href=\"https://wec.wiline.com/docs/es/tutorials/self-host-hermes-agent/#por-qu%C3%A9-importa-la-memoria-persistente\" class=\"hash-link\" aria-label=\"Enlace directo al Por qué importa la memoria persistente\" title=\"Enlace directo al Por qué importa la memoria persistente\" translate=\"no\">​</a></h2>\n<p>Un agente que olvida en cada reinicio es un juguete. Hermes mantiene un <strong>almacenamiento duradero en\ndisco</strong> bajo <code>~/.hermes/</code>:</p>\n<ul>\n<li class=\"\"><strong><code>~/.hermes/memories/MEMORY.md</code></strong> — un archivo legible por humanos de hechos que el agente ha\ndecidido recordar.</li>\n<li class=\"\"><strong><code>~/.hermes/state.db</code></strong> — una base de datos <strong>SQLite</strong> que contiene cada sesión y mensaje,\ncon índices de <strong>búsqueda de texto completo FTS5</strong> superpuestos para que el agente pueda buscar su propia\nhistoria.</li>\n</ul>\n<p>Un despliegue, un fallo o un reinicio no borran lo que aprendió. Esa es la diferencia entre\nuna demostración de chat y un agente en el que puedes confiar día a día — y es exactamente lo que probamos al\nfinal.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"las-partes-móviles\">Las partes móviles<a href=\"https://wec.wiline.com/docs/es/tutorials/self-host-hermes-agent/#las-partes-m%C3%B3viles\" class=\"hash-link\" aria-label=\"Enlace directo al Las partes móviles\" title=\"Enlace directo al Las partes móviles\" translate=\"no\">​</a></h2>\n<p>Todo se ejecuta en una Instancia WEC. Hermes se comunica con un modelo a través de la API de Inferencia WEC,\nmantiene su memoria en disco local y se ejecuta como un servicio de usuario systemd — por lo que se reinicia solo\ndespués de un reinicio y su memoria sigue ahí:</p>\n<!-- -->\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"requisitos-previos\">Requisitos previos<a href=\"https://wec.wiline.com/docs/es/tutorials/self-host-hermes-agent/#requisitos-previos\" class=\"hash-link\" aria-label=\"Enlace directo al Requisitos previos\" title=\"Enlace directo al Requisitos previos\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\">Una <strong>Instancia WEC</strong> existente con acceso a la shell (la caja de OpenClaw funciona).</li>\n<li class=\"\">Un modelo: ya sea un inicio de sesión en <strong>Nous Portal</strong>, o tu propia clave de API de proveedor (usamos una\nclave de OpenAI aquí; <strong>Modelos WEC</strong> con <code>glm-5.2</code> también funcionan).</li>\n<li class=\"\">Nada que preinstalar — el instalador maneja Python, Node.js y el motor\ndel navegador.</li>\n</ul>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-1--instalar-hermes\">Paso 1 — Instalar Hermes<a href=\"https://wec.wiline.com/docs/es/tutorials/self-host-hermes-agent/#paso-1--instalar-hermes\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 1 — Instalar Hermes\" title=\"Enlace directo al Paso 1 — Instalar Hermes\" translate=\"no\">​</a></h2>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-fsSL</span><span class=\"token plain\"> https://hermes-agent.nousresearch.com/install.sh </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">bash</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Ejecutando la línea de instalación — el instalador de Hermes comienza a resolver y descargar paquetes\" src=\"https://wec.wiline.com/docs/es/assets/images/hermes-install-start-1f3d29fcdd9d66dd1e5c1bc28b3c9e5d.png\" width=\"660\" height=\"341\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<p>El instalador resuelve un entorno virtual de Python con <code>uv</code>, instala las herramientas de navegador de Node.js, descarga un Chromium de Playwright, sincroniza las habilidades empaquetadas y coloca un\nlanzador <code>hermes</code> en <code>~/.local/bin</code>:</p>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">✓ Paquete principal instalado (verificado por hash a través de uv.lock)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">✓ Todas las dependencias instaladas</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">✓ Configuración del motor de navegador completa</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">✓ Lanzador hermes instalado → ~/.local/bin/hermes</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">✓ Directorio de configuración listo: ~/.hermes/</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">→ Iniciando asistente de configuración...</span><br></div></code></pre></div></div>\n<p>Luego lanza automáticamente el asistente de configuración. Recarga tu shell después para que el\ncomando <code>hermes</code> esté en tu <code>PATH</code>:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token builtin class-name\">source</span><span class=\"token plain\"> ~/.bashrc</span><br></div></code></pre></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-2--ejecutar-el-asistente-de-configuración\">Paso 2 — Ejecutar el asistente de configuración<a href=\"https://wec.wiline.com/docs/es/tutorials/self-host-hermes-agent/#paso-2--ejecutar-el-asistente-de-configuraci%C3%B3n\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 2 — Ejecutar el asistente de configuración\" title=\"Enlace directo al Paso 2 — Ejecutar el asistente de configuración\" translate=\"no\">​</a></h2>\n<p>El asistente guía a través del tipo de configuración, backend de terminal y mensajería. En una caja que\nya ejecutó OpenClaw, ofrece <strong>importar</strong> la configuración antigua primero — puedes previsualizar\nlo que se migraría antes de que cambie algo.</p>\n<p>La primera elección es <strong>cómo configurar Hermes</strong>:</p>\n<ul>\n<li class=\"\"><strong>Configuración Rápida (Nous Portal)</strong> — un inicio de sesión OAuth, sin claves de API. El Portal agrupa un\nmodelo y una puerta de enlace de herramientas. <strong>Requiere una suscripción activa a Nous Portal.</strong></li>\n<li class=\"\"><strong>Configuración completa (trae tus propias claves)</strong> — configuras proveedores y pegas tus propias\nclaves.</li>\n</ul>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El aviso &amp;quot;¿Cómo te gustaría configurar Hermes?&amp;quot; con opciones de Configuración Rápida, Configuración completa y Pizarra en blanco\" src=\"https://wec.wiline.com/docs/es/assets/images/hermes-setup-choice-0db3a3f64faec3d72e3477b075af1901.png\" width=\"658\" height=\"125\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<div class=\"theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z\"></path></svg></span>Lo que realmente hicimos</div><div class=\"admonitionContent_BuS1\"><p>Elegimos <strong>Configuración Rápida</strong> primero. El inicio de sesión en el Portal mostró un código de dispositivo y se quedó en\n<em>\"Esperando aprobación (sondeando)\"</em> — necesita una suscripción activa al Portal, que esta\ncuenta no tenía. Presionamos <strong>Ctrl+C</strong> para cancelar y en su lugar <strong>trajimos nuestro propio\nmodelo en el Paso 3</strong> (a continuación) con una clave de OpenAI. Si <em>tienes</em> un plan de Portal, completa\nel inicio de sesión aquí y puedes omitir el Paso 3.</p></div></div>\n<p>Para el resto del asistente:</p>\n<ul>\n<li class=\"\"><strong>Backend de terminal:</strong> <em>Local</em> — los comandos se ejecutan directamente en esta máquina.</li>\n<li class=\"\"><strong>Mensajería:</strong> <em>Omitir</em> por ahora (puedes conectar Telegram/Discord más tarde con\n<code>hermes setup gateway</code>).</li>\n</ul>\n<p>El asistente aplica valores predeterminados sensatos para el agente (máximo de iteraciones, un umbral de compresión de contexto del 50%) y termina con un resumen de disponibilidad de herramientas.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-3--conectar-un-modelo\">Paso 3 — Conectar un modelo<a href=\"https://wec.wiline.com/docs/es/tutorials/self-host-hermes-agent/#paso-3--conectar-un-modelo\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 3 — Conectar un modelo\" title=\"Enlace directo al Paso 3 — Conectar un modelo\" translate=\"no\">​</a></h2>\n<p>Fuera de la caja, el modelo predeterminado se enruta a través del Nous Portal / OpenRouter y necesita\nuna clave. Para usar <strong>tu propio proveedor</strong> en su lugar, ejecuta:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">hermes setup model</span><br></div></code></pre></div></div>\n<p>Luego selecciona <strong>OpenAI ▸ OpenAI API</strong>, pega tu clave de API cuando se te pida (se escribe en\n<code>~/.hermes/.env</code> — nunca se muestra), acepta la URL base predeterminada\n<code>https://api.openai.com/v1</code>, y elige un modelo — usamos <strong><code>gpt-5.5</code></strong>:</p>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Clave de API guardada.</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">URL base [https://api.openai.com/v1]:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Seleccionar modelo predeterminado:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"> → (●) gpt-5.5</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Eligiendo el modelo predeterminado en hermes setup model — gpt-5.5 seleccionado de la lista de OpenAI\" src=\"https://wec.wiline.com/docs/es/assets/images/hermes-model-select-2b0c5452d2e217ff8692c022badd9b28.png\" width=\"661\" height=\"545\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Usa Modelos WEC en su lugar</div><div class=\"admonitionContent_BuS1\"><p>Para ejecutar inferencias en el propio endpoint de WiLine, elige <strong>Endpoint personalizado</strong>, establece la URL base\nen <code>https://inference.wiline.com/v1</code>, pega tu clave de API de WEC y usa un modelo como\n<code>glm-5.2</code>. Mismo flujo compatible con OpenAI.</p></div></div>\n<p>Confirma que el modelo y la clave están saludables:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">hermes doctor</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">◆ Archivos de configuración</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  ✓ El archivo ~/.hermes/.env existe</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  ✓ Clave de API o endpoint personalizado configurado</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">...</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">◆ Proveedor de memoria</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  ✓ Memoria incorporada activa (no se configuró proveedor externo — esto está bien)</span><br></div></code></pre></div></div>\n<p>Las líneas restantes <code>⚠</code> en <code>doctor</code> son integraciones de herramientas opcionales (búsqueda web,\nDiscord, generación de imágenes) — no necesarias para esta guía.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-4--chatear-y-guardar-una-memoria\">Paso 4 — Chatear y guardar una memoria<a href=\"https://wec.wiline.com/docs/es/tutorials/self-host-hermes-agent/#paso-4--chatear-y-guardar-una-memoria\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 4 — Chatear y guardar una memoria\" title=\"Enlace directo al Paso 4 — Chatear y guardar una memoria\" translate=\"no\">​</a></h2>\n<p>Inicia una sesión interactiva:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">hermes</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"La pantalla de bienvenida del Agente Hermes TUI — logo ASCII, herramientas y habilidades disponibles, modelo gpt-5.5, y el aviso de chat\" src=\"https://wec.wiline.com/docs/es/assets/images/hermes-tui-welcome-842b22f5c733599e17b84cde75db4933.png\" width=\"657\" height=\"814\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<p>Envíale un hecho y pídele que lo recuerde — observa cómo se activa la herramienta <code>🧠 memory</code>:</p>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">En el TUI de Hermes</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">● Por favor guarda en tu memoria a largo plazo: mi nombre en clave del proyecto WEC es</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  Bluefin-7, ejecutándose en la caja de OpenClaw.</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  ┊ 🧠 memory    ?  0.0s</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">╭─ ⚕ Hermes ─────────────────────────────────────────────────────────╮</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    Guardado en la memoria a largo plazo: tu nombre en clave del proyecto WEC es Bluefin-7,</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    ejecutándose en la caja de OpenClaw.</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">╰─────────────────────────────────────────────────────────────────────╯</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Hermes activando la herramienta de memoria y confirmando que el hecho fue guardado en la memoria a largo plazo\" src=\"https://wec.wiline.com/docs/es/assets/images/hermes-memory-save-458cefa8aaf46e0d1cc554418a3acc38.png\" width=\"663\" height=\"218\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<p>Esa escritura crea <code>~/.hermes/memories/MEMORY.md</code>. Sal con <code>/exit</code> o <code>Ctrl+D</code>.</p>\n<div class=\"theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z\"></path></svg></span>Problema al pegar</div><div class=\"admonitionContent_BuS1\"><p>El TUI envía al presionar enter, por lo que pegar texto de varias líneas puede enviar un mensaje medio escrito.\nSi un prompt solo captura un fragmento, <strong>escríbelo en una línea</strong> a mano.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-5--probar-que-la-memoria-sobrevive-a-un-reinicio\">Paso 5 — Probar que la memoria sobrevive a un reinicio<a href=\"https://wec.wiline.com/docs/es/tutorials/self-host-hermes-agent/#paso-5--probar-que-la-memoria-sobrevive-a-un-reinicio\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 5 — Probar que la memoria sobrevive a un reinicio\" title=\"Enlace directo al Paso 5 — Probar que la memoria sobrevive a un reinicio\" translate=\"no\">​</a></h2>\n<p>Este es el objetivo principal. La puerta de enlace de Hermes está instalada como un <strong>servicio de usuario systemd</strong>\ncon <strong>linger habilitado</strong>, por lo que vuelve automáticamente después de un reinicio — sin necesidad de iniciar sesión.\nReinicia el servicio y luego reinicia toda la caja para hacer la prueba a prueba de fallos:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">systemctl </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--user</span><span class=\"token plain\"> restart hermes-gateway   </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># reinicia solo el servicio de puerta de enlace</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">sudo</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">reboot</span><span class=\"token plain\">                               </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># luego reinicia toda la caja</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Reiniciando la puerta de enlace y reiniciando la caja — la conexión SSH se cierra mientras el host se apaga\" src=\"https://wec.wiline.com/docs/es/assets/images/hermes-reboot-ae228254579427ae3018547803f83c62.png\" width=\"616\" height=\"74\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<p>Reconéctate una vez que la caja esté activa y confirma que el servicio se reinició por sí mismo:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">systemctl </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--user</span><span class=\"token plain\"> status hermes-gateway --no-pager</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">● hermes-gateway.service - Puerta de enlace del Agente Hermes - Integración de Plataforma de Mensajería</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">     Loaded: loaded (...; enabled; vendor preset: enabled)</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">     Active: active (running) since Thu 2026-06-25 21:21:54 UTC; 1min 36s ago</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"systemctl status mostrando hermes-gateway activo y en ejecución desde el arranque, iniciado automáticamente por linger después del reinicio\" src=\"https://wec.wiline.com/docs/es/assets/images/hermes-gateway-status-4da5472c10ec41fe81cfc64298589f10.png\" width=\"1001\" height=\"272\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<p>Ahora abre una sesión <strong>nueva</strong> y pregunta — sin contexto previo, ID de sesión completamente nuevo:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">hermes</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">En el TUI de Hermes</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">● ¿Cuál es mi nombre en clave del proyecto WEC, y en qué caja se ejecuta?</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">╭─ ⚕ Hermes ─────────────────────────────────────────────────────────╮</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    Tu nombre en clave del proyecto WEC es Bluefin-7, y se ejecuta en la</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    caja de OpenClaw.</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">╰─────────────────────────────────────────────────────────────────────╯</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Una nueva sesión de Hermes después del reinicio recordando correctamente el nombre en clave Bluefin-7 desde el disco\" src=\"https://wec.wiline.com/docs/es/assets/images/hermes-reboot-recall-eac2b093a48d12569fa730340f5751d3.png\" width=\"652\" height=\"300\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<p>Recordó el hecho desde el disco después de un reinicio completo. Inspecciona dónde vive ese estado duradero:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">cat</span><span class=\"token plain\"> ~/.hermes/memories/MEMORY.md</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">file</span><span class=\"token plain\"> ~/.hermes/state.db </span><span class=\"token operator\" style=\"color:#393A34\">&amp;&amp;</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">du</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-h</span><span class=\"token plain\"> ~/.hermes/state.db</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">El nombre en clave del proyecto WEC del usuario es Bluefin-7, ejecutándose en la caja de OpenClaw.</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">/home/ubuntu/.hermes/state.db: base de datos SQLite 3.x, ...</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">176K\t/home/ubuntu/.hermes/state.db</span><br></div></code></pre></div></div>\n<p>Un <code>MEMORY.md</code> en texto plano que puedes leer y editar, respaldado por un <code>state.db</code> de SQLite para\nsesiones. Esa es la memoria persistente — y acaba de sobrevivir a un reinicio.</p>\n<div class=\"theme-admonition theme-admonition-info admonition_xJq3 alert alert--info\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z\"></path></svg></span>Cómo se almacena la memoria — dos archivos, dos trabajos</div><div class=\"admonitionContent_BuS1\"><p>Cuando le pides a Hermes que recuerde algo, escribe el hecho en\n<strong><code>~/.hermes/memories/MEMORY.md</code></strong> — Markdown plano que puedes abrir, leer e incluso editar a mano. Por separado, tu <strong>historial completo de conversación</strong> se guarda en\n<strong><code>~/.hermes/state.db</code></strong>, una base de datos <strong>SQLite</strong>. Ambos viven en disco, por lo que un\nreinicio no los pierde.</p><p>La forma más sencilla de ver lo que sabe el agente es simplemente leer el Markdown:</p><div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">cat</span><span class=\"token plain\"> ~/.hermes/memories/MEMORY.md</span><br></div></code></pre></div></div><p>Para mirar dentro de la base de datos, ten en cuenta que el <em>motor</em> de SQLite está integrado en Python, por lo que Hermes\nlo usa sin ninguna instalación adicional. La opcional herramienta de línea de comandos <code>sqlite3</code> — para\nnavegar por la base de datos a mano — no está instalada por defecto. Si la quieres:</p><div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">sudo</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">apt</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">install</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-y</span><span class=\"token plain\"> sqlite3</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">sqlite3 ~/.hermes/state.db </span><span class=\"token string\" style=\"color:#e3116c\">'.tables'</span><br></div></code></pre></div></div><p><span class=\"zoomImage__wrap\"><img alt=\"Salida de .tables en state.db — tablas de sesiones y mensajes junto a la familia de mensajes_fts FTS5\" src=\"https://wec.wiline.com/docs/es/assets/images/hermes-sqlite-tables-dbdc5016a3514137534038cac3f6298d.png\" width=\"540\" height=\"170\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p><p>Verás tablas <code>sessions</code> y <code>messages</code> que contienen la conversación, además de una familia de\ntablas <code>messages_fts*</code>. Esos son índices de texto completo <strong>FTS5</strong> de SQLite — incluyendo una\nvariante de trigramas para coincidencias difusas — que permiten a Hermes buscar sus propias conversaciones pasadas:</p><div class=\"language-sql codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida (de .schema)</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-sql codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token keyword\" style=\"color:#00009f\">CREATE</span><span class=\"token plain\"> VIRTUAL </span><span class=\"token keyword\" style=\"color:#00009f\">TABLE</span><span class=\"token plain\"> messages_fts </span><span class=\"token keyword\" style=\"color:#00009f\">USING</span><span class=\"token plain\"> fts5</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">content</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">;</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token keyword\" style=\"color:#00009f\">CREATE</span><span class=\"token plain\"> VIRTUAL </span><span class=\"token keyword\" style=\"color:#00009f\">TABLE</span><span class=\"token plain\"> messages_fts_trigram </span><span class=\"token keyword\" style=\"color:#00009f\">USING</span><span class=\"token plain\"> fts5</span><span class=\"token punctuation\" style=\"color:#393A34\">(</span><span class=\"token plain\">content</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> tokenize</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token string\" style=\"color:#e3116c\">'trigram'</span><span class=\"token punctuation\" style=\"color:#393A34\">)</span><span class=\"token punctuation\" style=\"color:#393A34\">;</span><br></div></code></pre></div></div><p>Y cada sesión es una fila que puedes inspeccionar — aquí están las dos de esta guía, la\nsegunda creada <em>después</em> del reinicio:</p><div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">sqlite3 </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-header</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-column</span><span class=\"token plain\"> ~/.hermes/state.db </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token string\" style=\"color:#e3116c\">\"SELECT substr(id,1,15) AS id, model, message_count FROM sessions ORDER BY started_at;\"</span><br></div></code></pre></div></div><div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">id               model    message_count</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">---------------  -------  -------------</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">20260625_211227  gpt-5.5  8</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">20260625_212333  gpt-5.5  2</span><br></div></code></pre></div></div><p>Nunca <em>necesitas</em> la CLI para que Hermes funcione — es solo para inspeccionar la base de datos\ntú mismo. Día a día, el <code>MEMORY.md</code> legible por humanos es todo lo que tienes que mirar.</p></div></div>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Habilidad desbloqueada 🏅</div><div class=\"admonitionContent_BuS1\"><p>Ahora ejecutas un <strong>agente cuya memoria sobrevive a los reinicios</strong> — y sabes exactamente dónde vive esa\nmemoria en disco.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"bonificación--leer-la-conversación-que-almacenó-el-agente\">Bonificación — leer la conversación que almacenó el agente<a href=\"https://wec.wiline.com/docs/es/tutorials/self-host-hermes-agent/#bonificaci%C3%B3n--leer-la-conversaci%C3%B3n-que-almacen%C3%B3-el-agente\" class=\"hash-link\" aria-label=\"Enlace directo al Bonificación — leer la conversación que almacenó el agente\" title=\"Enlace directo al Bonificación — leer la conversación que almacenó el agente\" translate=\"no\">​</a></h2>\n<p>La tabla <code>messages</code> contiene el texto real de cada turno. Cada fila tiene un <code>role</code> —\n<code>user</code> (lo que escribiste), <code>assistant</code> (la respuesta de Hermes), o <code>tool</code> (el resultado de una herramienta) — para que\npuedas reproducir todo el intercambio:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">sqlite3 </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-header</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-column</span><span class=\"token plain\"> ~/.hermes/state.db </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token string\" style=\"color:#e3116c\">\"SELECT substr(session_id,10,6) AS sess, role, substr(content,1,55) AS content</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">   FROM messages ORDER BY timestamp;\"</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">sess    role       content</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">------  ---------  -------------------------------------------------------</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">211227  user       hello, can you hear me?</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">211227  assistant  Yes — I can hear you. How can I help?</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">211227  user       Please save to your long-term memory: my WEC project...</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">211227  tool       {\"success\": true, \"done\": true, \"target\": \"memory\", ...</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">211227  assistant  Saved to long-term memory: your WEC project codename...</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">212333  user       What's my WEC project codename, and which box does...</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">212333  assistant  Your WEC project codename is Bluefin-7, and it runs...</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"La tabla de mensajes en state.db mostrando la conversación completa — filas de usuario, asistente y herramienta a través de ambas sesiones\" src=\"https://wec.wiline.com/docs/es/assets/images/hermes-sqlite-messages-12ab80eeb7db27fbf02d5c73b075d553.png\" width=\"659\" height=\"294\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<p>La parte interesante es <em>cómo</em> se escribió la memoria. El asistente no editó un archivo\ndirectamente — emitió una <strong>llamada a la herramienta <code>memory</code></strong>, y esa herramienta es la que escribió\n<code>MEMORY.md</code>. Puedes ver la llamada exacta registrada en el historial:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">sqlite3 ~/.hermes/state.db </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token string\" style=\"color:#e3116c\">\"SELECT tool_calls FROM messages WHERE tool_calls LIKE '%memory%' LIMIT 1;\"</span><br></div></code></pre></div></div>\n<div class=\"language-json codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-json codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token property\" style=\"color:#36acaa\">\"function\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token property\" style=\"color:#36acaa\">\"name\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"memory\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> </span><span class=\"token property\" style=\"color:#36acaa\">\"arguments\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  \"</span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\">\\\"target\\\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\">\\\"memory\\\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\">\\\"operations\\\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token punctuation\" style=\"color:#393A34\">[</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">     </span><span class=\"token punctuation\" style=\"color:#393A34\">{</span><span class=\"token plain\">\\\"action\\\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\">\\\"add\\\"</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      \\\"content\\\"</span><span class=\"token operator\" style=\"color:#393A34\">:</span><span class=\"token plain\">\\\"El nombre en clave del proyecto WEC del usuario es Bluefin</span><span class=\"token number\" style=\"color:#36acaa\">-7</span><span class=\"token punctuation\" style=\"color:#393A34\">,</span><span class=\"token plain\"> ejecutándose en la caja de OpenClaw.\\\"</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">]</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token plain\">\"</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><span class=\"token punctuation\" style=\"color:#393A34\">}</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"La llamada a la herramienta de memoria registrada en la tabla de mensajes — la operación de adición que escribió el hecho de Bluefin-7 en MEMORY.md\" src=\"https://wec.wiline.com/docs/es/assets/images/hermes-sqlite-memory-toolcall-53bc3762b14f9ca8e30715118341b792.png\" width=\"897\" height=\"362\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<p>Así que la cadena completa es: <strong>tú escribes → el agente decide que vale la pena mantenerlo → llama a la\nherramienta <code>memory</code> con <code>action: add</code> → eso escribe el hecho en <code>MEMORY.md</code></strong>, mientras que\ntoda la conversación se mantiene en <code>state.db</code>, completamente buscable a través de FTS5. Dos almacenes, una\nmemoria duradera.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"qué-puedes-hacer-con-ello\">¿Qué puedes hacer con ello?<a href=\"https://wec.wiline.com/docs/es/tutorials/self-host-hermes-agent/#qu%C3%A9-puedes-hacer-con-ello\" class=\"hash-link\" aria-label=\"Enlace directo al ¿Qué puedes hacer con ello?\" title=\"Enlace directo al ¿Qué puedes hacer con ello?\" translate=\"no\">​</a></h2>\n<p>Una memoria que sobrevive a los reinicios solo importa por lo que el agente <em>hace</em> con ella.\nCon Hermes en funcionamiento, la misma instalación ofrece estas capacidades (las viste en la\nlista de herramientas de <code>hermes doctor</code>) — aquí está lo que desbloquean día a día:</p>\n<ul>\n<li class=\"\"><strong>Realiza trabajo real en la caja.</strong> Las herramientas <code>terminal</code> y <code>code_execution</code> le permiten actuar,\nno solo chatear — por ejemplo, <em>\"clona este repositorio, instala dependencias y ejecuta las pruebas,\"</em> o <em>\"verifica\npor qué se está llenando el disco.\"</em> Lo hace en la Instancia WEC y reporta de vuelta.</li>\n<li class=\"\"><strong>Controla desde tu teléfono.</strong> Una puerta de enlace de mensajería (Telegram, Discord, Slack, y\nmás) significa que puedes darle una tarea desde cualquier lugar — sin SSH. <em>(Configuración en la Parte 2.)</em></li>\n<li class=\"\"><strong>Ponlo en un horario.</strong> La herramienta <code>cronjob</code> ejecuta trabajos recurrentes: un <strong>informe matutino</strong>,\nun <strong>informe semanal de uso del disco</strong>, o <strong>vigila un servicio y avísame cuando falle</strong> — entregado a tu canal de mensajería. <em>(Parte 3.)</em></li>\n<li class=\"\"><strong>Navega y busca en la web.</strong> Con las herramientas de navegador y búsqueda web puede obtener información en vivo y actuar en páginas reales. <em>(Estas necesitan claves de API de proveedor — consulta <code>hermes setup tools</code>.)</em></li>\n<li class=\"\"><strong>Desarrolla habilidades reutilizables y delega.</strong> Puede construir <strong>habilidades</strong> a partir de lo que aprende y\ndelegar subtareas a <strong>subagentes</strong> — la parte \"agente que crece contigo\".</li>\n<li class=\"\"><strong>Recuerda a través de todo ello.</strong> Todo lo anterior está respaldado por la memoria persistente que\nacabas de configurar, por lo que el contexto se lleva de una tarea — y un día — al siguiente.</li>\n</ul>\n<p>El resto de esta serie convierte cada una de estas en una guía práctica (consulta <em>¿Qué sigue?</em>).</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"solución-de-problemas-real\">Solución de problemas (real)<a href=\"https://wec.wiline.com/docs/es/tutorials/self-host-hermes-agent/#soluci%C3%B3n-de-problemas-real\" class=\"hash-link\" aria-label=\"Enlace directo al Solución de problemas (real)\" title=\"Enlace directo al Solución de problemas (real)\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\">\n<p><strong>La Configuración Rápida (Nous Portal) se queda colgada en \"Esperando aprobación (sondeando)\".</strong> El inicio de sesión en el Portal\nnecesita una suscripción/crédito activo. Si no tienes uno, <code>Ctrl+C</code> y usa\n<em>Configuración completa</em> con tu propia clave en su lugar (Paso 3).</p>\n</li>\n<li class=\"\">\n<p><strong><code>doctor</code> muestra \"No se encontró clave de API en ~/.hermes/.env\".</strong> El modelo predeterminado apunta a\nPortal/OpenRouter. Ejecuta <code>hermes setup model</code>, elige tu proveedor y pega la\nclave — <code>doctor</code> luego informa <em>\"Clave de API o endpoint personalizado configurado.\"</em></p>\n</li>\n<li class=\"\">\n<p><strong>Advertencia de <code>.env</code> obsoleta al iniciar la puerta de enlace</strong> (por ejemplo, <code>MESSAGING_CWD</code>). Inofensiva;\nsigue la sugerencia para mover la configuración a <code>config.yaml</code> cuando sea conveniente.</p>\n</li>\n<li class=\"\">\n<p><strong>Repetidos <code>WARNING [Telegram] …</code> en los registros de la puerta de enlace.</strong> Si seguiste las partes anteriores\nde OpenClaw y agregaste un canal de Telegram, la configuración de Hermes detectó OpenClaw y\n<strong>importó esas configuraciones de mensajería</strong> en <code>~/.hermes/.env</code>. Como omitimos\nterminar la mensajería aquí, la puerta de enlace tiene un token pero no una configuración completa, por lo que sigue\nreintentando y registrando esta advertencia. (Una caja nueva sin configuración de Telegram de OpenClaw no\nlo verá.) Elimina las entradas sobrantes y reinicia:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">sed</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-i</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'/^TELEGRAM_ALLOWED_USERS=/d; /^TELEGRAM_BOT_TOKEN=/d'</span><span class=\"token plain\"> ~/.hermes/.env</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">systemctl </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--user</span><span class=\"token plain\"> restart hermes-gateway</span><br></div></code></pre></div></div>\n<p>Si ese token alguna vez fue expuesto, revócalo en <strong>@BotFather</strong> de Telegram y emite uno nuevo.</p>\n</li>\n</ul>\n<div class=\"skillComplete\"><span class=\"skillComplete__medal\" aria-hidden=\"true\">🎯</span><div class=\"skillComplete__text\"><div class=\"skillComplete__title\">Finished this tutorial?</div><div class=\"skillComplete__sub\">Mark it complete to earn <strong>Agent with persistent memory</strong> on your skill path.</div></div><button type=\"button\" class=\"skillComplete__btn\">Mark as complete ✓</button></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"qué-sigue\">¿Qué sigue?<a href=\"https://wec.wiline.com/docs/es/tutorials/self-host-hermes-agent/#qu%C3%A9-sigue\" class=\"hash-link\" aria-label=\"Enlace directo al ¿Qué sigue?\" title=\"Enlace directo al ¿Qué sigue?\" translate=\"no\">​</a></h2>\n<p>Ahora que Hermes está funcionando con memoria duradera, el resto de la serie convierte cada\ncapacidad anterior en una guía práctica:</p>\n<ul>\n<li class=\"\"><strong>Parte 2 — Controla a Hermes desde Telegram.</strong> Termina de migrar el canal de Telegram que Hermes\nya importó de OpenClaw (<code>hermes setup gateway</code>, listas de permitidos, verifica que el bot\nresponda), para que puedas darle tareas desde tu teléfono.</li>\n<li class=\"\"><strong>Parte 3 — Ponlo en un horario.</strong> Usa la herramienta cron para un informe matutino, un informe semanal,\ny una alerta de salud del servicio entregada a tu canal.</li>\n<li class=\"\"><strong>Parte 4 — Habilidades personalizadas y subagentes.</strong> Enséñale habilidades reutilizables y delega trabajo.</li>\n</ul>\n<p>Los enlaces aparecerán aquí a medida que cada parte se publique.</p>",
            "url": "https://wec.wiline.com/docs/es/tutorials/self-host-hermes-agent/",
            "title": "Autoalojar el Agente Hermes con memoria persistente",
            "summary": "Despliega el Agente Hermes de código abierto de Nous Research en tu Instancia WEC — con memoria persistente respaldada por SQLite que sobrevive a un reinicio completo. Instalación real, configuración del modelo y una prueba de memoria que sobrevive al reinicio.",
            "date_modified": "2026-06-25T00:00:00.000Z",
            "author": {
                "name": "Rafael Fernandes",
                "url": "https://www.linkedin.com/in/rafaelmacariofernandes/"
            },
            "tags": [
                "ai",
                "autoalojamiento",
                "hermes",
                "agentes",
                "memoria"
            ]
        },
        {
            "id": "https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/",
            "content_html": "<div class=\"tutorialHero\"><img class=\"tutorialHero__brand\" src=\"https://wec.wiline.com/docs/es/assets/images/openclaw-wordmark-003352a1a7f02afc3bd877ae6f2dc175.png\" alt=\"OpenClaw\"><span class=\"tutorialHero__plus\">+</span><img class=\"tutorialHero__docker\" src=\"https://wec.wiline.com/docs/es/assets/images/netbird-dark-0d2ba9c783792904def84b7e687b8f95.png\" alt=\"NetBird\"></div>\n<div class=\"skillTracker\" tabindex=\"0\" aria-label=\"Skill path: 0 of 6 skills earned\"><div class=\"skillTracker__ring\" style=\"--skillTracker-pct:0%\"><span class=\"skillTracker__ringLabel\">0<!-- -->/<!-- -->6</span></div><div class=\"skillTracker__panel\"><div class=\"skillTracker__header\"><span class=\"skillTracker__title\">🎯 Skill path</span><span class=\"skillTracker__count\">0<!-- -->/<!-- -->6<!-- --> earned</span></div><div class=\"skillTracker__series\">Self-hosting OpenClaw</div><ul class=\"skillTracker__steps\"><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/\"><span class=\"skillTracker__dot\" data-state=\"locked\">1</span><span class=\"skillTracker__skill\" data-state=\"locked\">Deploy your own AI assistant</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/\"><span class=\"skillTracker__dot\" data-state=\"locked\">2</span><span class=\"skillTracker__skill\" data-state=\"locked\">Real HTTPS + auth</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/add-telegram-channel-openclaw/\"><span class=\"skillTracker__dot\" data-state=\"locked\">3</span><span class=\"skillTracker__skill\" data-state=\"locked\">Chat from Telegram</span></a></li><li><span class=\"skillTracker__step\"><span class=\"skillTracker__dot\" data-state=\"current\">4</span><span class=\"skillTracker__skill\" data-state=\"current\">Private mesh access</span></span></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/run-openclaw-on-wec-models/\"><span class=\"skillTracker__dot\" data-state=\"locked\">5</span><span class=\"skillTracker__skill\" data-state=\"locked\">Run it on WEC models</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/add-whatsapp-channel-openclaw/\"><span class=\"skillTracker__dot\" data-state=\"locked\">🏆</span><span class=\"skillTracker__skill\" data-state=\"locked\">Chat from WhatsApp</span></a></li></ul><div class=\"skillTracker__footer\">Finish this tutorial, then mark it complete below to earn the skill.</div></div></div>\n<p>En <a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/\">Artículo 2</a> colocamos Caddy frente a\nOpenClaw para HTTPS, y en <a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/add-telegram-channel-openclaw/\">Artículo 3</a>\nagregamos un canal de Telegram. La puerta de enlace funciona — pero Caddy sigue escuchando en\n<code>0.0.0.0</code>, accesible por cualquier cosa que pueda enrutar hacia la caja. Este es el proyecto final\nde la serie: unimos el servidor y tu laptop a una <strong>malla NetBird</strong>,\nredirigimos <code>openclaw.local</code> a la IP de la malla y <strong>cerramos los puertos públicos</strong>. La misma\nURL <code>https://openclaw.local/chat</code> sigue funcionando — pero solo para tus dispositivos.\nCada comando y error a continuación proviene de la ejecución real.</p>\n<!-- -->\n<div class=\"theme-admonition theme-admonition-info admonition_xJq3 alert alert--info\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z\"></path></svg></span>Reproducibilidad</div><div class=\"admonitionContent_BuS1\"><p>Continúa desde los Artículos 1–3 en la misma Instancia WEC — <strong>Ubuntu</strong>, OpenClaw detrás de\n<strong>Caddy 2</strong>. Usamos <strong>NetBird gestionado</strong> (el nivel gratuito en\n<a href=\"https://app.netbird.io/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">app.netbird.io</a>) con el agente <strong>0.73.2</strong> tanto en el\nservidor (Linux, núcleo WireGuard) como en el cliente (macOS, espacio de usuario). NetBird es\n<a href=\"https://www.wireguard.com/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">WireGuard</a>-basado y de código abierto; también puedes\nautoalojar el plano de control, que es un tema más amplio para otro día.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"cómo-funciona-netbird-y-lo-que-construirás\">Cómo funciona NetBird (y lo que construirás)<a href=\"https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/#c%C3%B3mo-funciona-netbird-y-lo-que-construir%C3%A1s\" class=\"hash-link\" aria-label=\"Enlace directo al Cómo funciona NetBird (y lo que construirás)\" title=\"Enlace directo al Cómo funciona NetBird (y lo que construirás)\" translate=\"no\">​</a></h2>\n<p>NetBird tiene dos partes, y la distinción es el punto principal:</p>\n<ol>\n<li class=\"\"><strong>El plano de control</strong> (el <em>servicio de gestión</em> + <em>señal</em> gestionado de NetBird) — esta es la única cosa con una presencia pública. Cada dispositivo que quiere unirse <strong>se autentica</strong> (con tu clave de configuración o inicio de sesión SSO), y media el intercambio de la clave pública <a href=\"https://www.wireguard.com/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">WireGuard</a> de cada par.</li>\n<li class=\"\"><strong>El plano de datos</strong> — una vez autenticados, los pares se comunican <strong>directamente entre sí</strong>, cifrados de extremo a extremo a través de WireGuard. Tu tráfico real nunca fluye a través de un puerto público que gestionas; el plano de control solo hizo las presentaciones.</li>\n</ol>\n<p>Así que estás construyendo una malla donde <strong>Peer A</strong> (tu laptop) alcanza a <strong>Peer B</strong> (la caja OpenClaw) por su dirección privada <code>100.x</code> — y <code>openclaw.local</code> apunta allí, con Caddy vinculado solo a esa interfaz. El lado público/LAN no tiene nada escuchando.</p>\n<p>La idea clave para más adelante: <strong>la malla no está limitada a dos pares.</strong> Autentica una vez, y cada nuevo servicio — Ollama, un VDI, WordPress — se une como otro par con su propia dirección <code>100.x</code>, accesible de forma privada <strong>sin puertos públicos que abrir</strong>. Configuras el modelo de autenticación una vez; agregas servicios para siempre.</p>\n<!-- -->\n<p>El internet público solo llega a <strong>el plano de control de NetBird</strong> — y eso solo\n<em>autentica</em> pares, nunca transporta tu tráfico de aplicación. Tus servicios no están\n\"protegidos\" tanto como <strong>invisibles</strong>: OpenClaw no tiene IP pública, así que simplemente\n<strong>no hay ruta</strong> hacia él desde internet. La única forma de entrar es ser un\npar autenticado en la malla.</p>\n<p>En este tutorial conectamos los primeros dos pares — <strong>tu laptop</strong> y la\n<strong>caja OpenClaw</strong>. Los otros servicios hacen el punto: una vez que la autenticación está configurada,\n<code>Ollama</code>, un <code>VDI</code>, <code>WordPress</code> simplemente se unen a la misma malla como otro par <code>100.x</code>\n— sin puertos públicos, sin exposición por servicio.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"establecer-un-plano-de-control-de-netbird\">Establecer un plano de control de NetBird<a href=\"https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/#establecer-un-plano-de-control-de-netbird\" class=\"hash-link\" aria-label=\"Enlace directo al Establecer un plano de control de NetBird\" title=\"Enlace directo al Establecer un plano de control de NetBird\" translate=\"no\">​</a></h2>\n<p>Cada malla de NetBird tiene dos roles: <strong>pares</strong> — tus máquinas, es decir, la caja OpenClaw\nde las Partes 1–3 y tu laptop — y un <strong>plano de control</strong> que autentica esos\npares y emite sus claves de unión. Ya tienes el par (tu caja OpenClaw);\nahora necesitas un plano de control al que unirse. Dos formas de obtener uno:</p>\n<ul>\n<li class=\"\"><strong>Opción A — autoalojarlo en WiLine (recomendado).</strong> Despliega la <strong>plantilla de NetBird Marketplace</strong> de WEC: una <strong>Instancia WEC dedicada</strong> que ejecuta tu <em>propio</em> plano de control de NetBird — panel, certificado de confianza, cero shell. Ahora tu <em>entera</em> red privada vive en WiLine — la caja OpenClaw <strong>y</strong> su plano de control — sin dependencia de terceros.</li>\n<li class=\"\"><strong>Opción B — NetBird gestionado (sin caja extra).</strong> ¿No quieres ejecutar tu propio plano de control? Usa el nivel gratuito en <a href=\"https://app.netbird.io/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">app.netbird.io</a> — inicia sesión y listo; el plano de control es la nube de NetBird. Luego salta a <a href=\"https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/#now-enroll-your-machines\" class=\"\">Inscribir tus máquinas</a>.</li>\n</ul>\n<div class=\"theme-admonition theme-admonition-info admonition_xJq3 alert alert--info\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z\"></path></svg></span>Dos cajas, a propósito — y tu caja OpenClaw está intacta</div><div class=\"admonitionContent_BuS1\"><p>Con <strong>la Opción A</strong> ejecutarás <strong>dos instancias</strong>: tu <strong>caja OpenClaw</strong> (Partes 1–3,\nsin cambios) y una <strong>segunda caja de plano de control dedicada</strong> (esta plantilla). Esa es la\ntopología correcta — un plano de control debe ser su propia máquina, nunca compartiendo con una\ncarga de trabajo. Con <strong>la Opción B</strong> solo hay tu una caja OpenClaw. <strong>De cualquier manera nunca\nreconstruyes la caja OpenClaw</strong> — en <a href=\"https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/#now-enroll-your-machines\" class=\"\">Inscribir tus máquinas</a>\nsolo instalas el agente de NetBird en ella y se une a la malla como un par.</p></div></div>\n<p>Los pasos a continuación configuran <strong>la Opción A</strong>. WEC ejecuta la instalación; le das un correo electrónico y una\nIP pública, y unos minutos después tienes tu propio panel de NetBird en\n<code>https://&lt;your-ip&gt;.apps.wiline.cloud</code> con un certificado válido.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"1-obtener-una-ip-pública-elástica\">1. Obtener una IP pública (Elástica)<a href=\"https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/#1-obtener-una-ip-p%C3%BAblica-el%C3%A1stica\" class=\"hash-link\" aria-label=\"Enlace directo al 1. Obtener una IP pública (Elástica)\" title=\"Enlace directo al 1. Obtener una IP pública (Elástica)\" translate=\"no\">​</a></h3>\n<p>El panel necesita una dirección pública. Bajo <strong>Redes → IPs Elásticas</strong>, asigna una\nnueva IP o elige una libre, y anota su valor (la nuestra: <code>108.60.112.165</code>). Debe estar\nen la <strong>VPC y zona en la que desplegarás</strong>. Pasos completos:\n<a class=\"\" href=\"https://wec.wiline.com/docs/es/cloud_portal/platform/networks/elastic_ip/\">Configurar IPs Elásticas</a>.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Una IP Elástica disponible en Redes → IPs Elásticas\" src=\"https://wec.wiline.com/docs/es/assets/images/netbird-eip-available-b4ec56b4c4946226b7c9e199bd540f61.png\" width=\"2793\" height=\"1305\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"2-desplegar-una-vm-con-la-plantilla\">2. Desplegar una VM con la plantilla<a href=\"https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/#2-desplegar-una-vm-con-la-plantilla\" class=\"hash-link\" aria-label=\"Enlace directo al 2. Desplegar una VM con la plantilla\" title=\"Enlace directo al 2. Desplegar una VM con la plantilla\" translate=\"no\">​</a></h3>\n<p>Esta nueva instancia se convierte en tu <strong>plano de control de NetBird</strong> — una caja separada de tu\ninstancia OpenClaw, que permanece intacta. Abre <strong>Desplegar → Desplegar una VM</strong> (guía completa:\n<a class=\"\" href=\"https://wec.wiline.com/docs/es/cloud_portal/platform/compute/instances/compute_instance/\">Desplegar una Máquina Virtual</a>).\nLas elecciones que importan aquí:</p>\n<ul>\n<li class=\"\"><strong>Red</strong> — elige la red en la <strong>misma VPC/zona que tu IP Elástica</strong> (usamos <code>Subred Local</code> en <code>Princeton-VPC</code>), para que la IP pueda adjuntarse después.</li>\n<li class=\"\"><strong>Plantilla → Marketplace → Ubuntu 24.04 LTS Netbird.</strong></li>\n<li class=\"\"><strong>Configuración de Plantilla</strong> — tu <strong>correo electrónico de Let's Encrypt</strong> y la <strong>IP Elástica del paso 1</strong> como la dirección externa.</li>\n</ul>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Configuración de Plantilla — correo electrónico de Let&amp;#39;s Encrypt y la IP Elástica\" src=\"https://wec.wiline.com/docs/es/assets/images/netbird-template-config-e03939727bf27b7f6ce4ceb4fc5acdf1.png\" width=\"1968\" height=\"1240\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<p>Termina el asistente y despliega.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"3-adjuntar-la-ip-elástica-a-la-nueva-instancia\">3. Adjuntar la IP Elástica a la nueva instancia<a href=\"https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/#3-adjuntar-la-ip-el%C3%A1stica-a-la-nueva-instancia\" class=\"hash-link\" aria-label=\"Enlace directo al 3. Adjuntar la IP Elástica a la nueva instancia\" title=\"Enlace directo al 3. Adjuntar la IP Elástica a la nueva instancia\" translate=\"no\">​</a></h3>\n<p>La IP no está vinculada en el momento del despliegue — la adjuntas una vez que la VM esté activa. Ve a\n<strong>Redes → IPs Elásticas → tu IP → ⋯ → Asignar</strong>, y selecciona tu nueva instancia\n(<code>netbird-tutorial</code>). Solo las VMs en la <strong>misma VPC</strong> aparecen en la lista.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Asignando la IP Elástica a la nueva VM\" src=\"https://wec.wiline.com/docs/es/assets/images/netbird-eip-allocate-620e7b57084f55e9e8ca3323494cda6d.png\" width=\"1834\" height=\"1070\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"4-espera-el-primer-arranque-luego-verifica-que-esté-activa\">4. Espera el primer arranque, luego verifica que esté activa<a href=\"https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/#4-espera-el-primer-arranque-luego-verifica-que-est%C3%A9-activa\" class=\"hash-link\" aria-label=\"Enlace directo al 4. Espera el primer arranque, luego verifica que esté activa\" title=\"Enlace directo al 4. Espera el primer arranque, luego verifica que esté activa\" translate=\"no\">​</a></h3>\n<p>La instancia muestra <strong>Ejecutándose</strong> en <strong>Cómputo → Instancias</strong> en un minuto — pero la\nplantilla sigue trabajando durante unos minutos después de eso, descargando e iniciando los\ncontenedores de NetBird y levantando el panel. No esperes que la URL responda\ninmediatamente.</p>\n<p>Verifica desde tu <strong>laptop</strong> a través de HTTPS (no <code>ping</code> — ICMP está bloqueado, así que un\nping fallido no significa que esté caída):</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-skS</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-m</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">10</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-o</span><span class=\"token plain\"> /dev/null </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-w</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"%{http_code}</span><span class=\"token string entity\" style=\"color:#36acaa\">\\n</span><span class=\"token string\" style=\"color:#e3116c\">\"</span><span class=\"token plain\"> https://</span><span class=\"token operator\" style=\"color:#393A34\">&lt;</span><span class=\"token plain\">your-ip</span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\">.apps.wiline.cloud</span><br></div></code></pre></div></div>\n<ul>\n<li class=\"\"><code>000</code> / conexión rechazada → aún en provisión; espera un minuto y vuelve a intentar.</li>\n<li class=\"\"><code>301</code> luego <code>200</code> → está activa (HTTP redirige a HTTPS).</li>\n</ul>\n<p>Para verlo directamente, accede por SSH (<code>ssh ubuntu@&lt;your-ip&gt;</code>) y confirma que los contenedores están\nejecutándose y los puertos están vinculados:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">cloud-init status</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">sudo</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">ps</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">sudo</span><span class=\"token plain\"> ss </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-tlnp</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">grep</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-E</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">':(80|443)'</span><br></div></code></pre></div></div>\n<p>Está listo una vez que <strong><code>netbird-dashboard</code>, <code>netbird-traefik</code>, y <code>netbird-server</code></strong> muestran\n<strong>Arriba</strong> y <strong>80/443</strong> están escuchando.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"5-abre-el-panel-y-crea-tu-cuenta\">5. Abre el panel y crea tu cuenta<a href=\"https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/#5-abre-el-panel-y-crea-tu-cuenta\" class=\"hash-link\" aria-label=\"Enlace directo al 5. Abre el panel y crea tu cuenta\" title=\"Enlace directo al 5. Abre el panel y crea tu cuenta\" translate=\"no\">​</a></h3>\n<p>Abre <strong><code>https://&lt;your-ip&gt;.apps.wiline.cloud</code></strong> — haz clic en la advertencia del certificado\nsi recibes una (el mismo clic de paso de certificado autofirmado que en\n<a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/\">Artículo 2</a>).</p>\n<p>Este es <strong>tu propio</strong> plano de control de NetBird, ejecutándose en tu Instancia WEC. En la\nprimera visita te pide que <strong>crees la cuenta de administrador</strong> — ingresa un <strong>nombre, correo\nelectrónico y contraseña</strong>, haz clic en <strong>Crear</strong>, luego <strong>inicia sesión</strong> con esas mismas credenciales.\n(Esa pantalla de configuración aparece solo una vez; después el panel muestra una página normal de\n<strong>Iniciar sesión</strong>.)</p>\n<p>Llegas al panel de NetBird — tu plano de control privado en tu propio dominio:</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El panel de NetBird autoalojado\" src=\"https://wec.wiline.com/docs/es/assets/images/netbird-dashboard-c98eca329c445df75fee98ca55f230e9.png\" width=\"2850\" height=\"1646\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<p>Haz clic en <strong>Agregar Par</strong> y NetBird te entrega el comando de inscripción para <em>tu</em> servidor —\nnota el <code>--management-url</code> apuntando a tu propio dominio:</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Agregar Par en el panel autoalojado — nota la bandera --management-url\" src=\"https://wec.wiline.com/docs/es/assets/images/netbird-add-peer-09fd78007bcc0279cae91217c1786221.png\" width=\"2827\" height=\"1599\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-fsSL</span><span class=\"token plain\"> https://pkgs.netbird.io/install.sh </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">sh</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">netbird up --management-url https://</span><span class=\"token operator\" style=\"color:#393A34\">&lt;</span><span class=\"token plain\">your-ip</span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\">.apps.wiline.cloud</span><br></div></code></pre></div></div>\n<p>(Ese segundo comando abre un inicio de sesión SSO en el navegador. Para un servidor <strong>sin cabeza</strong>,\nagrega <code>--setup-key &lt;KEY&gt;</code> desde la página de <strong>Claves de Configuración</strong> de tu panel en su lugar — igual que el flujo gestionado, solo que con <code>--management-url</code> agregado.)</p>\n<p>Este es el panel donde creas las claves de configuración que inscriben máquinas — que es exactamente lo que hacemos a continuación.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"ahora-inscribe-tus-máquinas\">Ahora inscribe tus máquinas<a href=\"https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/#ahora-inscribe-tus-m%C3%A1quinas\" class=\"hash-link\" aria-label=\"Enlace directo al Ahora inscribe tus máquinas\" title=\"Enlace directo al Ahora inscribe tus máquinas\" translate=\"no\">​</a></h2>\n<p>Tienes un plano de control (tu propio panel de plantilla, o NetBird gestionado). Ahora coloca\nlas dos máquinas en la malla — la <strong>caja OpenClaw</strong> de las Partes 1–3 y tu\n<strong>laptop</strong> — y luego cierra los puertos públicos de la caja. Estos pasos son los mismos sin importar qué plano de control elegiste.</p>\n<div class=\"theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z\"></path></svg></span>De dónde proviene tu clave de configuración</div><div class=\"admonitionContent_BuS1\"><p>Los comandos a continuación utilizan <strong>NetBird gestionado</strong> (<code>app.netbird.io</code>) como ejemplo. Si\ndesplegaste la <strong>plantilla de WEC</strong> en su lugar, es idéntico excepto: crea la clave de configuración\nen <strong>tu propio</strong> panel (<code>https://&lt;your-ip&gt;.apps.wiline.cloud</code>) y agrega\n<strong><code>--management-url https://&lt;your-ip&gt;.apps.wiline.cloud</code></strong> a <code>netbird up</code>, para que el\nagente apunte a <em>tu</em> servidor en lugar de a la nube gestionada.</p></div></div>\n<p><strong>Requisitos previos:</strong> OpenClaw ejecutándose detrás de Caddy de\n<a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/\">Artículos 1–3</a> (accesible en\n<code>https://openclaw.local</code>), un plano de control del paso anterior, y acceso a la shell en\nla caja más administrador en tu laptop.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-1--instalar-el-agente-de-netbird-en-la-caja-openclaw\">Paso 1 — Instalar el agente de NetBird en la caja OpenClaw<a href=\"https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/#paso-1--instalar-el-agente-de-netbird-en-la-caja-openclaw\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 1 — Instalar el agente de NetBird en la caja OpenClaw\" title=\"Enlace directo al Paso 1 — Instalar el agente de NetBird en la caja OpenClaw\" translate=\"no\">​</a></h2>\n<p>Esta es la caja de las Partes 1–3 (una Instancia WEC) — la estamos inscribiendo como un <strong>par</strong>,\nque es separado de cualquier servidor de plano de control. NetBird envía un instalador de una línea:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-fsSL</span><span class=\"token plain\"> https://pkgs.netbird.io/install.sh </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">sh</span><br></div></code></pre></div></div>\n<p><strong>Problema (real):</strong> en Ubuntu la instalación activa el diálogo <code>needrestart</code> —\nuna caja azul <em>\"¿Qué servicios deberían reiniciarse?\"</em>. <code>Tab</code> entre botones puede ser\nabsorbido por algunas configuraciones de SSH/terminal; usa <strong>las teclas de flecha</strong> para moverte,\n<strong>Espacio</strong> para alternar, <strong>Enter</strong> para confirmar (<code>&lt;Ok&gt;</code> es el predeterminado). El paquete ya está\nen su lugar para cuando aparece esta caja, así que incluso si sales con <code>Ctrl+C</code>, el agente\nestá instalado — volver a ejecutar el instalador solo te lo dirá:</p>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">NetBird parece estar ya instalado, por favor elimínalo antes de continuar</span><br></div></code></pre></div></div>\n<p>(Si vuelves a ejecutar el instalador después de este tutorial, verás en su lugar\n<code>El servicio de NetBird está en ejecución, por favor deténlo antes de continuar</code> — el agente ya está\nactivo, así que no hay nada que reinstalar.)</p>\n<p>Confirma la versión:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">netbird version</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># 0.73.2</span><br></div></code></pre></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-2--crear-una-clave-de-configuración-y-unirse-a-la-caja-openclaw\">Paso 2 — Crear una clave de configuración y unirse a la caja OpenClaw<a href=\"https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/#paso-2--crear-una-clave-de-configuraci%C3%B3n-y-unirse-a-la-caja-openclaw\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 2 — Crear una clave de configuración y unirse a la caja OpenClaw\" title=\"Enlace directo al Paso 2 — Crear una clave de configuración y unirse a la caja OpenClaw\" translate=\"no\">​</a></h2>\n<p>Una <strong>clave de configuración</strong> es el token que inscribe un dispositivo en tu malla — ideal para un\nservidor sin cabeza (sin necesidad de SSO en el navegador).</p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>¿Primera vez en el panel? Salta los desvíos</div><div class=\"admonitionContent_BuS1\"><p>Algunas cosas intentarán enviarte en la dirección equivocada en una cuenta nueva — aquí está la\nlínea directa:</p><ul>\n<li class=\"\"><strong>¿Sin cuenta aún?</strong> Crea una primero (inicia sesión con Google/GitHub/correo electrónico). No puedes generar una clave hasta que estés dentro.</li>\n<li class=\"\"><strong>El asistente de incorporación</strong> (\"Agrega tu primer recurso\" — <em>IP única / Subred completa / Dominio</em>) aparece de inmediato. <strong>Sáltalo.</strong> Los recursos son para exponer subredes/dominios; solo necesitamos dos pares simples, así que no es necesario.</li>\n<li class=\"\"><strong>El botón naranja \"Agregar Par\"</strong> abre el <strong>flujo de instalación SSO</strong> (descargar la aplicación, registrarse con correo electrónico, conectarse desde la bandeja). Eso está destinado a un escritorio con un navegador — <strong>no</strong> a un servidor sin cabeza. No lo sigas.</li>\n<li class=\"\">En su lugar, ve directamente a la página de <strong>Claves de Configuración</strong>: <strong><code>https://app.netbird.io/setup-keys</code></strong>.</li>\n</ul></div></div>\n<ol>\n<li class=\"\">Abre <strong><code>https://app.netbird.io/setup-keys</code></strong>.</li>\n<li class=\"\"><strong>Crear Clave de Configuración</strong> → nombre <code>openclaw-tutorial</code>, alterna <strong>Reutilizable</strong> (para que funcione también para la laptop), por defecto de otro modo.</li>\n<li class=\"\">Copia la clave.</li>\n</ol>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Creando una clave de configuración reutilizable en el panel de NetBird\" src=\"https://wec.wiline.com/docs/es/assets/images/netbird-setup-key-ade9af748bded2998a18890a1ebde93b.png\" width=\"1898\" height=\"950\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<p>Conecta la VM (reemplaza con tu clave):</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">sudo</span><span class=\"token plain\"> netbird up --setup-key </span><span class=\"token operator\" style=\"color:#393A34\">&lt;</span><span class=\"token plain\">YOUR_SETUP_KEY</span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><br></div></code></pre></div></div>\n<div class=\"theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z\"></path></svg></span>Trata la clave de configuración como un secreto</div><div class=\"admonitionContent_BuS1\"><p>Cualquiera con una clave reutilizable puede agregar dispositivos a tu red. No la pegues en\nchats o commits, y <strong>elimínala</strong> del panel una vez que tus dispositivos estén\ninscritos (los pares existentes permanecen conectados).</p></div></div>\n<p>Verifica la conexión:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">netbird status</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"estado de netbird en la VM — Gestión y Señal Conectadas, IP de malla 100.87.239.229, conteo de Pares 0/0\" src=\"https://wec.wiline.com/docs/es/assets/images/netbird-vm-status-8e8088e406d7392203f1eb5da63b4258.png\" width=\"731\" height=\"374\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<p>Esa <strong><code>IP de NetBird</code> (<code>100.87.239.229</code>)</strong> es la dirección a la que apunta todo lo que sigue.\n<code>Conteo de Pares: 0/0</code> es esperado — nada más se ha unido aún.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-3--unir-tu-laptop\">Paso 3 — Unir tu laptop<a href=\"https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/#paso-3--unir-tu-laptop\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 3 — Unir tu laptop\" title=\"Enlace directo al Paso 3 — Unir tu laptop\" translate=\"no\">​</a></h2>\n<p>Instala el cliente de NetBird en lo que estés usando, luego conéctate con la\n<strong>misma clave de configuración reutilizable</strong>. El comando de conexión (<code>netbird up --setup-key …</code>) es\nidéntico en cada SO — solo la instalación difiere.</p>\n<div class=\"theme-admonition theme-admonition-info admonition_xJq3 alert alert--info\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z\"></path></svg></span>Lo ejecutamos en macOS</div><div class=\"admonitionContent_BuS1\"><p>La salida capturada a continuación es de un Mac. Las pestañas de Windows y Linux dan la\ninstalación equivalente; los pasos de <code>netbird up</code>, <code>netbird status</code>, y ping son los\nmismos en todas partes (Windows usa <code>ping -n</code> en lugar de <code>-c</code>).</p></div></div>\n<div class=\"theme-tabs-container tabs-container tabList_zI5C\"><ul role=\"tablist\" aria-orientation=\"horizontal\" class=\"tabs\"><li role=\"tab\" tabindex=\"0\" aria-selected=\"true\" class=\"tabs__item tabItem_yeRP tabs__item--active\">macOS</li><li role=\"tab\" tabindex=\"-1\" aria-selected=\"false\" class=\"tabs__item tabItem_yeRP\">Windows</li><li role=\"tab\" tabindex=\"-1\" aria-selected=\"false\" class=\"tabs__item tabItem_yeRP\">Linux</li></ul><div class=\"margin-top--md\"><div role=\"tabpanel\" class=\"tabItem_yrzq\"><p>Instala a través de Homebrew, luego inicia el servicio y conéctate:</p><div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">brew </span><span class=\"token function\" style=\"color:#d73a49\">install</span><span class=\"token plain\"> netbirdio/tap/netbird</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">sudo</span><span class=\"token plain\"> netbird </span><span class=\"token function\" style=\"color:#d73a49\">service</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">install</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">sudo</span><span class=\"token plain\"> netbird </span><span class=\"token function\" style=\"color:#d73a49\">service</span><span class=\"token plain\"> start</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">netbird up --setup-key </span><span class=\"token operator\" style=\"color:#393A34\">&lt;</span><span class=\"token plain\">YOUR_SETUP_KEY</span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><br></div></code></pre></div></div><p>Puede que veas una advertencia de gRPC única mientras el socket del daemon se activa un momento después\nde que el cliente lo llama — termina en <code>Conectado</code>, así que es inofensivo:</p><div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">WARNING: ... dial unix /var/run/netbird.sock: connect: no such file or directory</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Connected</span><br></div></code></pre></div></div></div><div role=\"tabpanel\" class=\"tabItem_yrzq\" hidden=\"\"><p>Descarga y ejecuta el <strong>instalador de NetBird para Windows</strong> desde\n<a href=\"https://app.netbird.io/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">app.netbird.io</a> (Agregar Par → Windows) o\n<a href=\"https://pkgs.netbird.io/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">pkgs.netbird.io</a>. Instala una aplicación en la bandeja del sistema y el\nCLI <code>netbird</code>. Luego, en <strong>PowerShell como Administrador</strong>, conéctate con tu clave:</p><div class=\"language-powershell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-powershell codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">netbird up --setup-key &lt;YOUR_SETUP_KEY&gt;</span><br></div></code></pre></div></div><p>(También puedes hacer clic en <strong>Conectar</strong> desde el icono de la bandeja y autenticarte a través de SSO, pero\nla clave de configuración es el mismo flujo de un solo uso que en las otras plataformas.)</p></div><div role=\"tabpanel\" class=\"tabItem_yrzq\" hidden=\"\"><p>El mismo instalador de una línea que la VM, luego conéctate:</p><div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-fsSL</span><span class=\"token plain\"> https://pkgs.netbird.io/install.sh </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">sh</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">sudo</span><span class=\"token plain\"> netbird up --setup-key </span><span class=\"token operator\" style=\"color:#393A34\">&lt;</span><span class=\"token plain\">YOUR_SETUP_KEY</span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><br></div></code></pre></div></div></div></div></div>\n<p>Verifica que los dos nodos se vean entre sí, y que el túnel transporte tráfico:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">netbird status</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">ping</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-c</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">3</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">100.87</span><span class=\"token plain\">.239.229   </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># Windows: ping -n 3 100.87.239.229</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Estado de netbird en Mac mostrando Conteo de Pares 1/1 Conectado, además de un ping exitoso a la IP de malla de la VM\" src=\"https://wec.wiline.com/docs/es/assets/images/netbird-mac-status-857d291924930e23af4e6550b1845ec7.png\" width=\"609\" height=\"475\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<p><code>Conteo de Pares: 1/1</code> — ese par es la VM. En el panel, ambas máquinas ahora\naparecen bajo <strong>Pares</strong> (porque las inscribimos con una clave de configuración en lugar de un\ninicio de sesión SSO, aparecen bajo <strong>Servidores</strong> en lugar de <strong>Dispositivos de Usuario</strong>):</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"La VM y la laptop conectadas como pares en el panel de NetBird\" src=\"https://wec.wiline.com/docs/es/assets/images/netbird-peers-a54440db77e8715febacc8d410c24da5.png\" width=\"1895\" height=\"951\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<div class=\"theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z\"></path></svg></span>¿Por qué ~200 ms?</div><div class=\"admonitionContent_BuS1\"><p>Este camino está pasando por un <strong>relay</strong> de NetBird (nota <code>Tipo de Interfaz: Espacio de Usuario</code>\nen el Mac). Está bien para una interfaz web, y NetBird a menudo actualizará a un enlace directo\nde par a par cuando las redes lo permitan. La latencia a un par retransmitido no es una\nbandera roja.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-4--redirigir-openclawlocal-a-la-ip-de-la-malla\">Paso 4 — Redirigir <code>openclaw.local</code> a la IP de la malla<a href=\"https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/#paso-4--redirigir-openclawlocal-a-la-ip-de-la-malla\" class=\"hash-link\" aria-label=\"Enlace directo al paso-4--redirigir-openclawlocal-a-la-ip-de-la-malla\" title=\"Enlace directo al paso-4--redirigir-openclawlocal-a-la-ip-de-la-malla\" translate=\"no\">​</a></h2>\n<p>Desde <a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/\">Artículo 2</a>, tu archivo de hosts mapea\n<code>openclaw.local</code> a la IP LAN de la VM (<code>10.80.4.212</code>). Cámbiala por la IP de la malla\n(<code>100.87.239.229</code>). La ruta del archivo y el comando de edición difieren según el SO:</p>\n<div class=\"theme-tabs-container tabs-container tabList_zI5C\"><ul role=\"tablist\" aria-orientation=\"horizontal\" class=\"tabs\"><li role=\"tab\" tabindex=\"0\" aria-selected=\"true\" class=\"tabs__item tabItem_yeRP tabs__item--active\">macOS</li><li role=\"tab\" tabindex=\"-1\" aria-selected=\"false\" class=\"tabs__item tabItem_yeRP\">Linux</li><li role=\"tab\" tabindex=\"-1\" aria-selected=\"false\" class=\"tabs__item tabItem_yeRP\">Windows</li></ul><div class=\"margin-top--md\"><div role=\"tabpanel\" class=\"tabItem_yrzq\"><p><code>sed</code> en macOS necesita el vacío <code>-i ''</code>:</p><div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">grep</span><span class=\"token plain\"> openclaw /etc/hosts</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># 10.80.4.212 openclaw.local</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">sudo</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">sed</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-i</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">''</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'s/^10\\.80\\.4\\.212 openclaw\\.local/100.87.239.229 openclaw.local/'</span><span class=\"token plain\"> /etc/hosts</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">grep</span><span class=\"token plain\"> openclaw /etc/hosts</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># 100.87.239.229 openclaw.local</span><br></div></code></pre></div></div></div><div role=\"tabpanel\" class=\"tabItem_yrzq\" hidden=\"\"><p>GNU <code>sed</code> toma <code>-i</code> sin argumento:</p><div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">grep</span><span class=\"token plain\"> openclaw /etc/hosts</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">sudo</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">sed</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-i</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">'s/^10\\.80\\.4\\.212 openclaw\\.local/100.87.239.229 openclaw.local/'</span><span class=\"token plain\"> /etc/hosts</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">grep</span><span class=\"token plain\"> openclaw /etc/hosts</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># 100.87.239.229 openclaw.local</span><br></div></code></pre></div></div></div><div role=\"tabpanel\" class=\"tabItem_yrzq\" hidden=\"\"><p>El archivo de hosts está en <code>C:\\Windows\\System32\\drivers\\etc\\hosts</code> y necesita admin.\nEn <strong>PowerShell como Administrador</strong>:</p><div class=\"language-powershell codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-powershell codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">$h = \"$env:WINDIR\\System32\\drivers\\etc\\hosts\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">(Get-Content $h) -replace '^10\\.80\\.4\\.212 openclaw\\.local','100.87.239.229 openclaw.local' | Set-Content $h</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Select-String openclaw $h</span><br></div></code></pre></div></div><p>(O abre ese archivo en <strong>Notepad ejecutado como Administrador</strong> y cambia la IP a mano.)</p></div></div></div>\n<p>Prueba a través de la malla — <em>antes</em> de tocar ningún puerto:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-kI</span><span class=\"token plain\"> https://openclaw.local/</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">HTTP/2 200</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">...</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">via: 1.1 Caddy</span><br></div></code></pre></div></div>\n<p><code>via: 1.1 Caddy</code> a través de la IP de la malla — el chat ahora fluye a través de NetBird. Carga\n<code>https://openclaw.local/chat?session=main</code> en el navegador para confirmar que se comporta\nexactamente como antes. El certificado TLS sigue coincidiendo porque el nombre de host no ha cambiado;\nsolo la IP detrás de él se movió.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El chat de OpenClaw cargando a través de la malla en https://openclaw.local\" src=\"https://wec.wiline.com/docs/es/assets/images/netbird-chat-mesh-409321edd81ea8d59656891246bc0558.png\" width=\"1900\" height=\"993\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-5--cerrar-los-puertos-públicos\">Paso 5 — Cerrar los puertos públicos<a href=\"https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/#paso-5--cerrar-los-puertos-p%C3%BAblicos\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 5 — Cerrar los puertos públicos\" title=\"Enlace directo al Paso 5 — Cerrar los puertos públicos\" translate=\"no\">​</a></h2>\n<p>Aquí está el estado que estamos corrigiendo. En la VM:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">sudo</span><span class=\"token plain\"> ss </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-tlnp</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">grep</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-E</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">':(80|443)'</span><br></div></code></pre></div></div>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockTitle_OeMC\">Salida — escuchando en 0.0.0.0 (todas las interfaces)</div><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">LISTEN 0 4096  0.0.0.0:443  ...  docker-proxy</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">LISTEN 0 4096  0.0.0.0:80   ...  docker-proxy</span><br></div></code></pre></div></div>\n<p>Caddy escucha en <strong><code>0.0.0.0</code></strong> — todas las interfaces, incluyendo la LAN (y cualquier NAT\ndelante de ella).</p>\n<div class=\"theme-admonition theme-admonition-danger admonition_xJq3 alert alert--danger\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M5.05.31c.81 2.17.41 3.38-.52 4.31C3.55 5.67 1.98 6.45.9 7.98c-1.45 2.05-1.7 6.53 3.53 7.7-2.2-1.16-2.67-4.52-.3-6.61-.61 2.03.53 3.33 1.94 2.86 1.39-.47 2.3.53 2.27 1.67-.02.78-.31 1.44-1.13 1.81 3.42-.59 4.78-3.42 4.78-5.56 0-2.84-2.53-3.22-1.25-5.61-1.52.13-2.03 1.13-1.89 2.75.09 1.08-1.02 1.8-1.86 1.33-.67-.41-.66-1.19-.06-1.78C8.18 5.31 8.68 2.45 5.05.32L5.03.3l.02.01z\"></path></svg></span>La trampa de ufw</div><div class=\"admonitionContent_BuS1\"><p>Tu primer instinto es <code>ufw deny 443</code>. <strong>No funcionará.</strong> Los puertos de Caddy son\npublicados por <strong>Docker</strong>, que escribe sus propias reglas de iptables que evitan la\ncadena <code>INPUT</code> de ufw. Agregarías reglas de ufw, te sentirías seguro, y 443 seguiría abierto. En\nesta caja <code>ufw</code> estaba incluso <code>inactivo</code> — y habilitarlo no habría cambiado nada\npara los puertos publicados por Docker.</p></div></div>\n<p>La solución confiable es <strong>vincular los puertos publicados a la IP de la malla</strong> en lugar de\n<code>0.0.0.0</code>, para que Caddy solo escuche en <code>wt0</code>. Edita el servicio de Caddy en\n<code>docker-compose.override.yml</code> (la sobreescritura del Artículo 2):</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token builtin class-name\">cd</span><span class=\"token plain\"> /home/ubuntu/openclaw</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">cp</span><span class=\"token plain\"> docker-compose.override.yml docker-compose.override.yml.bak</span><br></div></code></pre></div></div>\n<p>Cambia el bloque de puertos de:</p>\n<div class=\"language-yaml codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-yaml codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">ports</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"80:80\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"443:443\"</span><br></div></code></pre></div></div>\n<p>a vincular a la IP de la malla:</p>\n<div class=\"language-yaml codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-yaml codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">    </span><span class=\"token key atrule\" style=\"color:#00a4db\">ports</span><span class=\"token punctuation\" style=\"color:#393A34\">:</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"100.87.239.229:80:80\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      </span><span class=\"token punctuation\" style=\"color:#393A34\">-</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"100.87.239.229:443:443\"</span><br></div></code></pre></div></div>\n<p>Recrea solo Caddy y vuelve a verificar:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose up </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> caddy</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">sudo</span><span class=\"token plain\"> ss </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-tlnp</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">grep</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-E</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">':(80|443)'</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"ss después del cambio — Caddy ahora escuchando solo en la IP de la malla 100.87.239.229, ya no en 0.0.0.0\" src=\"https://wec.wiline.com/docs/es/assets/images/netbird-ss-after-0172e6fc62bc667a141b1f6528526e73.png\" width=\"745\" height=\"103\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<p>Los puertos han desaparecido de <code>0.0.0.0</code> — ahora viven solo en la interfaz de malla.</p>\n<div class=\"theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z\"></path></svg></span>Orden de inicio</div><div class=\"admonitionContent_BuS1\"><p>Debido a que el enlace apunta a la IP de la malla, <strong>NetBird debe estar activo antes de que Caddy inicie</strong>\n(por ejemplo, después de un reinicio, <code>wt0</code> necesita su IP antes de que Docker se vincule a ella). La\npolítica <code>restart: unless-stopped</code> ya cubre esto — Caddy reintenta hasta que\nla interfaz exista — pero vale la pena saberlo si alguna vez ves a Caddy en un bucle de\ncrash justo después del arranque.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-6--demostrar-el-bloqueo\">Paso 6 — Demostrar el bloqueo<a href=\"https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/#paso-6--demostrar-el-bloqueo\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 6 — Demostrar el bloqueo\" title=\"Enlace directo al Paso 6 — Demostrar el bloqueo\" translate=\"no\">​</a></h2>\n<p>Ambas direcciones, de la ejecución real.</p>\n<p><strong>Aún accesible a través de la malla</strong> (laptop):</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-kI</span><span class=\"token plain\"> https://openclaw.local/</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># HTTP/2 200 ... via: 1.1 Caddy</span><br></div></code></pre></div></div>\n<p><strong>Ya no accesible en la LAN</strong> (VM, golpeando su propia IP LAN):</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-kI</span><span class=\"token plain\"> --connect-timeout </span><span class=\"token number\" style=\"color:#36acaa\">5</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--resolve</span><span class=\"token plain\"> openclaw.local:443:10.80.4.212 https://openclaw.local/</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"En la VM, curl a la antigua IP LAN ahora devuelve Conexión rechazada\" src=\"https://wec.wiline.com/docs/es/assets/images/netbird-refused-0944b25666489470ae7b961e2d6243c8.png\" width=\"617\" height=\"55\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<p>Ese es el resultado: <strong>la misma URL, accesible solo a través de tu malla, rechazada\nen todas partes.</strong></p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Habilidad desbloqueada 🏅</div><div class=\"admonitionContent_BuS1\"><p>Ahora puedes <strong>sacar cualquier servicio autoalojado de internet público</strong> sin romper cómo lo\nusas.</p></div></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"limpieza\">Limpieza<a href=\"https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/#limpieza\" class=\"hash-link\" aria-label=\"Enlace directo al Limpieza\" title=\"Enlace directo al Limpieza\" translate=\"no\">​</a></h2>\n<p>Elimina la clave de configuración reutilizable ahora que ambos dispositivos están inscritos —\n<strong>Claves de Configuración → eliminar</strong> en el panel. Tus pares permanecen conectados; solo\nestás cerrando la puerta que usaste para agregarlos. Para eliminar un dispositivo más tarde, elimínalo bajo\n<strong>Pares → Dispositivos de Usuario</strong> y ejecuta <code>netbird down</code> en esa máquina.</p>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"solución-de-problemas-real\">Solución de problemas (real)<a href=\"https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/#soluci%C3%B3n-de-problemas-real\" class=\"hash-link\" aria-label=\"Enlace directo al Solución de problemas (real)\" title=\"Enlace directo al Solución de problemas (real)\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\"><strong>El diálogo <code>needrestart</code> no acepta <code>Tab</code>.</strong> Usa teclas de flecha / Espacio / Enter. El paquete ya está instalado para cuando aparece la caja.</li>\n<li class=\"\"><strong>Advertencia de gRPC <code>netbird.sock</code> en el primer <code>up</code>.</strong> El socket del daemon se retrasa un momento respecto al cliente. Si termina en <code>Conectado</code>, ignórala.</li>\n<li class=\"\"><strong>Las reglas de <code>ufw</code> no bloquean la puerta de enlace.</strong> Los puertos publicados por Docker evitan <code>ufw</code> — vincula el puerto a una IP específica (como arriba) o usa la propia cadena <code>DOCKER-USER</code> de Docker.</li>\n<li class=\"\"><strong>Caddy en bucle de crash después de reiniciar.</strong> Se inició antes de que <code>wt0</code> tuviera la IP de la malla. <code>restart: unless-stopped</code> lo recupera; o ordena Docker después de <code>netbird.service</code>.</li>\n<li class=\"\"><strong>Ping alto a un par.</strong> Estás retransmitido, no directo — funcional, solo más lento. Verifica <code>Tipo de Interfaz</code> y el detalle del par de NetBird para el tipo de conexión.</li>\n</ul>\n<div class=\"skillComplete\"><span class=\"skillComplete__medal\" aria-hidden=\"true\">🎯</span><div class=\"skillComplete__text\"><div class=\"skillComplete__title\">Finished this tutorial?</div><div class=\"skillComplete__sub\">Mark it complete to earn <strong>Private mesh access</strong> on your skill path.</div></div><button type=\"button\" class=\"skillComplete__btn\">Mark as complete ✓</button></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"qué-sigue\">¿Qué sigue?<a href=\"https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/#qu%C3%A9-sigue\" class=\"hash-link\" aria-label=\"Enlace directo al ¿Qué sigue?\" title=\"Enlace directo al ¿Qué sigue?\" translate=\"no\">​</a></h2>\n<p>Eso cierra la serie de <strong>Autoalojamiento de OpenClaw</strong>:</p>\n<ol>\n<li class=\"\"><a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/\">Desplegar OpenClaw en una Instancia WEC a través de Docker Compose</a></li>\n<li class=\"\"><a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/\">Asegurar OpenClaw con un proxy inverso Caddy + HTTPS</a></li>\n<li class=\"\"><a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/add-telegram-channel-openclaw/\">Agregar un canal de Telegram a OpenClaw</a></li>\n<li class=\"\"><strong>Haz que OpenClaw sea privado con una VPN de malla NetBird</strong> ← estás aquí</li>\n</ol>\n<p>Ahora tienes un agente autoalojado que está cifrado en tránsito, emparejado con tus\ndispositivos, accesible desde tu teléfono, e invisible para el internet público — todo en\nuna pequeña Instancia WEC.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"continúa-el-viaje\">Continúa el viaje<a href=\"https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/#contin%C3%BAa-el-viaje\" class=\"hash-link\" aria-label=\"Enlace directo al Continúa el viaje\" title=\"Enlace directo al Continúa el viaje\" translate=\"no\">​</a></h3>\n<p>¿Listo para un agente que <em>recuerda</em>? Comienza la serie de <strong>Autoalojamiento de Hermes</strong> —\n<strong><a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/self-host-hermes-agent/\">Autoalojar el Agente Hermes con memoria persistente</a></strong> —\ndesplegado en esta misma Instancia WEC, con contexto que sobrevive a los reinicios.</p>",
            "url": "https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/",
            "title": "Haz que OpenClaw sea privado con una VPN de malla NetBird",
            "summary": "Coloca OpenClaw en una malla privada con NetBird, redirige tu nombre de host a la IP de la malla y cierra los puertos públicos — así la misma URL de chat funciona solo para tus dispositivos. Comandos reales y problemas de una ejecución en vivo.",
            "date_modified": "2026-06-24T00:00:00.000Z",
            "author": {
                "name": "Rafael Fernandes",
                "url": "https://www.linkedin.com/in/rafaelmacariofernandes/"
            },
            "tags": [
                "ai",
                "autoalojamiento",
                "openclaw",
                "netbird",
                "vpn",
                "malla",
                "seguridad"
            ]
        },
        {
            "id": "https://wec.wiline.com/docs/es/tutorials/add-telegram-channel-openclaw/",
            "content_html": "<div class=\"tutorialHero\"><img class=\"tutorialHero__brand\" src=\"https://wec.wiline.com/docs/es/assets/images/openclaw-wordmark-003352a1a7f02afc3bd877ae6f2dc175.png\" alt=\"OpenClaw\"><span class=\"tutorialHero__plus\">+</span><svg viewBox=\"0 0 24 24\" fill=\"#26A5E4\" aria-label=\"Telegram\" class=\"tutorialHero__docker\"><path d=\"M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z\"></path></svg></div>\n<div class=\"skillTracker\" tabindex=\"0\" aria-label=\"Skill path: 0 of 6 skills earned\"><div class=\"skillTracker__ring\" style=\"--skillTracker-pct:0%\"><span class=\"skillTracker__ringLabel\">0<!-- -->/<!-- -->6</span></div><div class=\"skillTracker__panel\"><div class=\"skillTracker__header\"><span class=\"skillTracker__title\">🎯 Skill path</span><span class=\"skillTracker__count\">0<!-- -->/<!-- -->6<!-- --> earned</span></div><div class=\"skillTracker__series\">Self-hosting OpenClaw</div><ul class=\"skillTracker__steps\"><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/\"><span class=\"skillTracker__dot\" data-state=\"locked\">1</span><span class=\"skillTracker__skill\" data-state=\"locked\">Deploy your own AI assistant</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/\"><span class=\"skillTracker__dot\" data-state=\"locked\">2</span><span class=\"skillTracker__skill\" data-state=\"locked\">Real HTTPS + auth</span></a></li><li><span class=\"skillTracker__step\"><span class=\"skillTracker__dot\" data-state=\"current\">3</span><span class=\"skillTracker__skill\" data-state=\"current\">Chat from Telegram</span></span></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/\"><span class=\"skillTracker__dot\" data-state=\"locked\">4</span><span class=\"skillTracker__skill\" data-state=\"locked\">Private mesh access</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/run-openclaw-on-wec-models/\"><span class=\"skillTracker__dot\" data-state=\"locked\">5</span><span class=\"skillTracker__skill\" data-state=\"locked\">Run it on WEC models</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/add-whatsapp-channel-openclaw/\"><span class=\"skillTracker__dot\" data-state=\"locked\">🏆</span><span class=\"skillTracker__skill\" data-state=\"locked\">Chat from WhatsApp</span></a></li></ul><div class=\"skillTracker__footer\">Finish this tutorial, then mark it complete below to earn the skill.</div></div></div>\n<p>Has <a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/\">desplegado OpenClaw</a> y\n<a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/\">lo has asegurado detrás de HTTPS</a>. Ahora hazlo\n<em>utilizable</em> — habla con tu agente desde tu teléfono a través de Telegram. Creamos un bot,\nlo conectamos, limpiamos la puerta de emparejamiento de OpenClaw y obtenemos una respuesta real. Cada comando y\ndetalle a continuación proviene de una ejecución real.</p>\n<!-- -->\n<div class=\"theme-admonition theme-admonition-info admonition_xJq3 alert alert--info\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z\"></path></svg></span>Reproducibilidad</div><div class=\"admonitionContent_BuS1\"><p>Continúa desde las Partes 1–2 en la misma Instancia WEC — OpenClaw <strong>2026.6.8</strong>. Telegram\nusa <strong>polling</strong> (la puerta de enlace consulta la API de Telegram), por lo que no se requiere un webhook o dominio público — esto funciona en una caja privada.</p></div></div>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"lo-que-construirás\">Lo que construirás<a href=\"https://wec.wiline.com/docs/es/tutorials/add-telegram-channel-openclaw/#lo-que-construir%C3%A1s\" class=\"hash-link\" aria-label=\"Enlace directo al Lo que construirás\" title=\"Enlace directo al Lo que construirás\" translate=\"no\">​</a></h2>\n<p>Tu teléfono habla con un bot de Telegram; la puerta de enlace de OpenClaw consulta Telegram para\nmensajes, los procesa a través de tu modelo y responde — no se necesitan puertos de entrada.</p>\n<!-- -->\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"requisitos-previos\">Requisitos previos<a href=\"https://wec.wiline.com/docs/es/tutorials/add-telegram-channel-openclaw/#requisitos-previos\" class=\"hash-link\" aria-label=\"Enlace directo al Requisitos previos\" title=\"Enlace directo al Requisitos previos\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\"><strong><a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/\">Parte 1</a></strong> + <strong><a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/\">Parte 2</a></strong> completadas — OpenClaw ejecutándose en <code>~/openclaw</code></li>\n<li class=\"\">Una <strong>cuenta de Telegram</strong> y un <strong>cliente de Telegram conectado</strong> — la aplicación de teléfono/escritorio, o <code>web.telegram.org</code>. (No puedes crear un bot desde la página web pública <code>t.me</code>; consulta <a href=\"https://wec.wiline.com/docs/es/tutorials/add-telegram-channel-openclaw/#troubleshooting-real-errors\" class=\"\">Solución de problemas</a>.)</li>\n</ul>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-1--crear-un-bot-de-telegram\">Paso 1 — Crear un bot de Telegram<a href=\"https://wec.wiline.com/docs/es/tutorials/add-telegram-channel-openclaw/#paso-1--crear-un-bot-de-telegram\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 1 — Crear un bot de Telegram\" title=\"Enlace directo al Paso 1 — Crear un bot de Telegram\" translate=\"no\">​</a></h2>\n<p>En la aplicación de Telegram, toca <strong>buscar</strong> y escribe <strong><code>BotFather</code></strong>, luego abre la cuenta oficial — la que tiene el <strong>check azul</strong>.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Buscando a BotFather en Telegram\" src=\"https://wec.wiline.com/docs/es/assets/images/tg-step1a-search-922bcf2cacaf250ac10c1a03546f240b.png\" width=\"1100\" height=\"2381\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<p>Toca <strong>Iniciar</strong> — BotFather responde con los comandos que entiende (verás\nopciones como <code>/newbot</code>, <code>/mybots</code>, <code>/token</code>, y más). Envía <strong><code>/newbot</code></strong>, luego\nsigue sus dos indicaciones:</p>\n<ol>\n<li class=\"\"><strong>Nombre</strong> — un nombre para mostrar, por ejemplo, <code>Mi Agente OpenClaw</code></li>\n<li class=\"\"><strong>Nombre de usuario</strong> — debe ser único y terminar en <code>bot</code>, por ejemplo, <code>wiline_openclaw_bot</code></li>\n</ol>\n<p>BotFather confirma con <em>\"¡Listo! Felicitaciones por tu nuevo bot\"</em> y te da el\n<strong>token</strong> — la línea después de <code>Usa este token para acceder a la API HTTP:</code>, como\n<code>1234567890:AA...</code>.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"BotFather emitiendo el token del bot\" src=\"https://wec.wiline.com/docs/es/assets/images/tg-step1c-token-0bec47ec554ad66500f6d053bd0a4810.png\" width=\"756\" height=\"1316\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<div class=\"theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z\"></path></svg></span>El token del bot es una credencial</div><div class=\"admonitionContent_BuS1\"><p>Cualquiera que lo tenga controla tu bot. <strong>Difumínalo en la captura de pantalla anterior</strong>, y <code>/revoke</code>\nen BotFather si alguna vez se filtra.</p></div></div>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-2--conectar-el-bot-a-openclaw\">Paso 2 — Conectar el bot a OpenClaw<a href=\"https://wec.wiline.com/docs/es/tutorials/add-telegram-channel-openclaw/#paso-2--conectar-el-bot-a-openclaw\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 2 — Conectar el bot a OpenClaw\" title=\"Enlace directo al Paso 2 — Conectar el bot a OpenClaw\" translate=\"no\">​</a></h2>\n<p>En la <strong>VM</strong>, en <code>~/openclaw</code>, registra el canal con tu token:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose run </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rm</span><span class=\"token plain\"> openclaw-cli channels </span><span class=\"token function\" style=\"color:#d73a49\">add</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--channel</span><span class=\"token plain\"> telegram </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--token</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"&lt;tu-token-de-bot&gt;\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># Cuenta de Telegram \"default\" añadida.</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"añadiendo el canal con éxito\" src=\"data:image/png;base64,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\" width=\"641\" height=\"242\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-3--activar-y-verificar\">Paso 3 — Activar y verificar<a href=\"https://wec.wiline.com/docs/es/tutorials/add-telegram-channel-openclaw/#paso-3--activar-y-verificar\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 3 — Activar y verificar\" title=\"Enlace directo al Paso 3 — Activar y verificar\" translate=\"no\">​</a></h2>\n<p>Reinicia la puerta de enlace para que reconozca el canal, luego verifica el estado:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose restart openclaw-gateway</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose run </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rm</span><span class=\"token plain\"> openclaw-cli channels status</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># - Telegram default: habilitado, configurado, en ejecución, modo:polling, token:config</span><br></div></code></pre></div></div>\n<p><code>modo:polling</code> significa que la puerta de enlace está consultando activamente Telegram — no se requiere puerto de entrada o webhook.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"estado de los canales mostrando Telegram funcionando en modo polling\" src=\"https://wec.wiline.com/docs/es/assets/images/tg-step3-status-6393f1690ec9b270312c2f5e3a25f137.png\" width=\"642\" height=\"392\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-4--mensaje-al-bot-puerta-de-emparejamiento\">Paso 4 — Mensaje al bot (puerta de emparejamiento)<a href=\"https://wec.wiline.com/docs/es/tutorials/add-telegram-channel-openclaw/#paso-4--mensaje-al-bot-puerta-de-emparejamiento\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 4 — Mensaje al bot (puerta de emparejamiento)\" title=\"Enlace directo al Paso 4 — Mensaje al bot (puerta de emparejamiento)\" translate=\"no\">​</a></h2>\n<p>Abre tu bot (<code>t.me/&lt;tu-nombre-de-bot&gt;</code>), toca <strong>Iniciar</strong>, y envía un mensaje.</p>\n<p>OpenClaw trata a los remitentes desconocidos como no confiables — así que en lugar de responder, el bot\ndevuelve un <strong>código de emparejamiento</strong> y tu id de usuario de Telegram. Este es el mismo patrón de seguridad\nque el emparejamiento de dispositivos en la Parte 2.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Bot respondiendo con el código de emparejamiento\" src=\"https://wec.wiline.com/docs/es/assets/images/tg-step4-pairing-94e777f1790fbeadab0cd42be384b80b.png\" width=\"1100\" height=\"2381\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-5--aprobar-el-emparejamiento\">Paso 5 — Aprobar el emparejamiento<a href=\"https://wec.wiline.com/docs/es/tutorials/add-telegram-channel-openclaw/#paso-5--aprobar-el-emparejamiento\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 5 — Aprobar el emparejamiento\" title=\"Enlace directo al Paso 5 — Aprobar el emparejamiento\" translate=\"no\">​</a></h2>\n<p>En la <strong>VM</strong>, aprueba el código del mensaje del bot:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose run </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rm</span><span class=\"token plain\"> openclaw-cli pairing approve telegram </span><span class=\"token operator\" style=\"color:#393A34\">&lt;</span><span class=\"token plain\">código-de-emparejamiento</span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># Remitente de telegram aprobado &lt;id&gt;.</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># Propietario del comando configurado telegram:&lt;id&gt; (commands.ownerAllowFrom estaba vacío).</span><br></div></code></pre></div></div>\n<div class=\"theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z\"></path></svg></span>nota</div><div class=\"admonitionContent_BuS1\"><p>Aprobar al primer usuario como <strong>propietario del comando</strong> cambia la configuración que requiere un\nreinicio de la puerta de enlace — OpenClaw lo hace <strong>automáticamente</strong> (verás\n<code>cambio de configuración requiere reinicio de la puerta de enlace</code> luego el reinicio del proveedor de Telegram en los\nregistros). Espera unos segundos para que vuelva antes de enviar otro mensaje.</p></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Aprobando el emparejamiento en la VM — &amp;quot;Remitente de telegram aprobado …&amp;quot;\" src=\"data:image/png;base64,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\" width=\"639\" height=\"240\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-6--chatea-con-tu-agente\">Paso 6 — Chatea con tu agente<a href=\"https://wec.wiline.com/docs/es/tutorials/add-telegram-channel-openclaw/#paso-6--chatea-con-tu-agente\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 6 — Chatea con tu agente\" title=\"Enlace directo al Paso 6 — Chatea con tu agente\" translate=\"no\">​</a></h2>\n<p>De vuelta en Telegram, simplemente habla con él — no se necesita configuración adicional; OpenClaw con tu modelo\nresponde de manera conversacional desde el principio. Prueba algo como:</p>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">En una oración, ¿cuál es el objetivo de poner un proxy inverso frente a mi agente?</span><br></div></code></pre></div></div>\n<p>Responde con una respuesta real — la cadena completa está activa: tu teléfono → Telegram →\npuerta de enlace → OpenAI → respuesta. Tu agente autoalojado ahora está en tu bolsillo. (Proporcionarle\nherramientas y habilidades vendrá en guías posteriores; el chat conversacional funciona de inmediato.)</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El agente respondiendo de manera conversacional a través de Telegram\" src=\"https://wec.wiline.com/docs/es/assets/images/tg-step6-working-7acac4effbd1c03d0657534125e3ea3b.png\" width=\"1100\" height=\"2381\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Habilidad desbloqueada 🏅</div><div class=\"admonitionContent_BuS1\"><p>Tu agente ahora está <strong>en tu bolsillo</strong> — una línea privada de Telegram a tu propia IA, sin necesidad de un webhook público.</p></div></div>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"solución-de-problemas-errores-reales\">Solución de problemas (errores reales)<a href=\"https://wec.wiline.com/docs/es/tutorials/add-telegram-channel-openclaw/#soluci%C3%B3n-de-problemas-errores-reales\" class=\"hash-link\" aria-label=\"Enlace directo al Solución de problemas (errores reales)\" title=\"Enlace directo al Solución de problemas (errores reales)\" translate=\"no\">​</a></h2>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"1-botfather-iniciar-no-hace-nada-en-un-navegador\">1. BotFather \"Iniciar\" no hace nada en un navegador<a href=\"https://wec.wiline.com/docs/es/tutorials/add-telegram-channel-openclaw/#1-botfather-iniciar-no-hace-nada-en-un-navegador\" class=\"hash-link\" aria-label=\"Enlace directo al 1. BotFather &quot;Iniciar&quot; no hace nada en un navegador\" title=\"Enlace directo al 1. BotFather &quot;Iniciar&quot; no hace nada en un navegador\" translate=\"no\">​</a></h3>\n<p>Abrir <code>t.me/BotFather</code> en un navegador web simple muestra un botón de <strong>Iniciar</strong> que no hace\nnada — esa página es solo la tarjeta pública del bot y necesita un <strong>cliente de Telegram conectado</strong>\npara poder transferir. Usa la aplicación de escritorio/teléfono, o inicia sesión en\n<code>web.telegram.org</code> (vincula un dispositivo de escritorio escaneando el QR desde tu teléfono:\nConfiguración → Dispositivos → Vincular dispositivo de escritorio), luego envía un mensaje a BotFather allí.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"2-el-bot-devuelve-un-código-de-emparejamiento-en-lugar-de-responder\">2. El bot devuelve un código de emparejamiento en lugar de responder<a href=\"https://wec.wiline.com/docs/es/tutorials/add-telegram-channel-openclaw/#2-el-bot-devuelve-un-c%C3%B3digo-de-emparejamiento-en-lugar-de-responder\" class=\"hash-link\" aria-label=\"Enlace directo al 2. El bot devuelve un código de emparejamiento en lugar de responder\" title=\"Enlace directo al 2. El bot devuelve un código de emparejamiento en lugar de responder\" translate=\"no\">​</a></h3>\n<p>No es un error — es la <strong>puerta de emparejamiento</strong>. Los remitentes desconocidos deben ser aprobados primero\n(Paso 5). Después de la aprobación, el bot chatea normalmente.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"3-proxy-headers-detected-from-untrusted-address\">3. <code>Proxy headers detected from untrusted address</code><a href=\"https://wec.wiline.com/docs/es/tutorials/add-telegram-channel-openclaw/#3-proxy-headers-detected-from-untrusted-address\" class=\"hash-link\" aria-label=\"Enlace directo al 3-proxy-headers-detected-from-untrusted-address\" title=\"Enlace directo al 3-proxy-headers-detected-from-untrusted-address\" translate=\"no\">​</a></h3>\n<p>Si seguiste la Parte 2, la puerta de enlace registra esto porque Caddy está delante de ella.\nEs una advertencia, no un fallo. Para silenciarlo y restaurar la detección correcta del cliente,\nconfigura <code>gateway.trustedProxies</code> a la red de tu proxy.</p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"notas-de-seguridad-y-dimensionamiento\">Notas de seguridad y dimensionamiento<a href=\"https://wec.wiline.com/docs/es/tutorials/add-telegram-channel-openclaw/#notas-de-seguridad-y-dimensionamiento\" class=\"hash-link\" aria-label=\"Enlace directo al Notas de seguridad y dimensionamiento\" title=\"Enlace directo al Notas de seguridad y dimensionamiento\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\"><strong>El emparejamiento es la puerta</strong> — solo los usuarios de Telegram aprobados llegan al agente; el primer\nusuario aprobado se convierte en el propietario del comando.</li>\n<li class=\"\"><strong>Polling, no webhooks</strong> — no se abre ningún puerto de entrada, lo que mantiene la\nprotección de la Parte 2 intacta.</li>\n<li class=\"\"><strong>Dimensionamiento</strong> — en una caja de 2 vCPU, la puerta de enlace registró <code>event loop degraded</code> bajo carga.\nPara un bot ocupado, dale más CPU/RAM.</li>\n</ul>\n<hr>\n<div class=\"skillComplete\"><span class=\"skillComplete__medal\" aria-hidden=\"true\">🎯</span><div class=\"skillComplete__text\"><div class=\"skillComplete__title\">Finished this tutorial?</div><div class=\"skillComplete__sub\">Mark it complete to earn <strong>Chat from Telegram</strong> on your skill path.</div></div><button type=\"button\" class=\"skillComplete__btn\">Mark as complete ✓</button></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"qué-sigue\">¿Qué sigue?<a href=\"https://wec.wiline.com/docs/es/tutorials/add-telegram-channel-openclaw/#qu%C3%A9-sigue\" class=\"hash-link\" aria-label=\"Enlace directo al ¿Qué sigue?\" title=\"Enlace directo al ¿Qué sigue?\" translate=\"no\">​</a></h2>\n<p>Has terminado <strong>Parte 3</strong> de la serie <strong>Autoalojamiento de OpenClaw</strong>. A continuación:</p>\n<ul>\n<li class=\"\"><strong>Parte 4 — <a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/\">Haz que OpenClaw sea privado con una VPN de malla NetBird</a></strong> <em>(siguiente)</em> — coloca tu agente en una malla privada y cierra los puertos públicos.</li>\n</ul>\n<p>Luego comienza la serie <strong>Autoalojamiento de Hermes</strong> — <strong><a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/self-host-hermes-agent/\">Autoalojar el Agente Hermes con memoria persistente</a></strong> — para un agente cuyo contexto sobrevive a los reinicios.</p>\n<p>Los archivos complementarios para la serie se encuentran en el repositorio de manifiestos de WiLine <em>(enlace que vendrá con el repositorio)</em>.</p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"desmantelamiento\">Desmantelamiento<a href=\"https://wec.wiline.com/docs/es/tutorials/add-telegram-channel-openclaw/#desmantelamiento\" class=\"hash-link\" aria-label=\"Enlace directo al Desmantelamiento\" title=\"Enlace directo al Desmantelamiento\" translate=\"no\">​</a></h2>\n<p>Elimina el canal de Telegram (mantiene el resto de OpenClaw en funcionamiento):</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose run </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rm</span><span class=\"token plain\"> openclaw-cli channels remove </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--channel</span><span class=\"token plain\"> telegram</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose restart openclaw-gateway</span><br></div></code></pre></div></div>\n<p>Y <code>/revoke</code> el token del bot en BotFather si has terminado con él.</p>",
            "url": "https://wec.wiline.com/docs/es/tutorials/add-telegram-channel-openclaw/",
            "title": "Agregar un canal de Telegram a OpenClaw",
            "summary": "Habla con tu agente OpenClaw autoalojado desde tu teléfono. Crea un bot de Telegram, conéctalo, limpia la puerta de emparejamiento y chatea — capturado de una ejecución real.",
            "date_modified": "2026-06-23T00:00:00.000Z",
            "author": {
                "name": "Rafael Fernandes",
                "url": "https://www.linkedin.com/in/rafaelmacariofernandes/"
            },
            "tags": [
                "ai",
                "autoalojamiento",
                "openclaw",
                "telegram",
                "chatbot"
            ]
        },
        {
            "id": "https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/",
            "content_html": "<div class=\"tutorialHero\"><img class=\"tutorialHero__brand\" src=\"https://wec.wiline.com/docs/es/assets/images/openclaw-wordmark-003352a1a7f02afc3bd877ae6f2dc175.png\" alt=\"OpenClaw\"><span class=\"tutorialHero__plus\">+</span><svg viewBox=\"0 0 24 24\" fill=\"#1F88C0\" aria-label=\"Caddy\" class=\"tutorialHero__docker\"><path d=\"M11.094.47c-.842 0-1.696.092-2.552.288a11.37 11.37 0 0 0-4.87 2.423 10.632 10.632 0 0 0-2.36 2.826A10.132 10.132 0 0 0 .305 8.582c-.398 1.62-.4 3.336-.043 5.048.085.405.183.809.31 1.212a11.85 11.85 0 0 0 1.662 3.729 3.273 3.273 0 0 0-.086.427 3.323 3.323 0 0 0 2.848 3.71 3.279 3.279 0 0 0 1.947-.346c1.045.51 2.17.864 3.339 1.04a11.66 11.66 0 0 0 4.285-.155 11.566 11.566 0 0 0 4.936-2.485 10.643 10.643 0 0 0 2.352-2.894 11.164 11.164 0 0 0 1.356-4.424 11.214 11.214 0 0 0-.498-4.335c.175-.077.338-.175.486-.293a.444.444 89.992 0 0 .001 0c.402-.322.693-.794.777-1.342a2.146 2.146 0 0 0-1.79-2.434 2.115 2.115 0 0 0-1.205.171c-.038-.043-.078-.086-.113-.13a11.693 11.693 0 0 0-3.476-2.93 13.348 13.348 0 0 0-1.76-.81 13.55 13.55 0 0 0-2.06-.613A12.121 12.121 0 0 0 11.093.47Zm.714.328c.345-.004.688.01 1.028.042a9.892 9.892 0 0 1 2.743.639c.984.39 1.89.958 2.707 1.632.803.662 1.502 1.45 2.091 2.328.026.039.048.08.07.12a2.12 2.12 0 0 0-.435 2.646c-.158.114-.97.692-1.634 1.183-.414.308-.733.557-.733.557l.581.68s.296-.276.665-.638c.572-.562 1.229-1.233 1.395-1.403a2.122 2.122 0 0 0 1.907.677 11.229 11.229 0 0 1-.013 4.046 11.41 11.41 0 0 1-1.475 3.897 12.343 12.343 0 0 1-2.079 2.587c-1.19 1.125-2.633 2.022-4.306 2.531a10.826 10.826 0 0 1-3.973.484 11.04 11.04 0 0 1-3.057-.652 3.304 3.304 0 0 0 1.417-2.294 3.275 3.275 0 0 0-.294-1.842c.18-.162.403-.363.656-.6 1.015-.955 2.353-2.303 2.353-2.303l-.47-.599s-1.63.972-2.801 1.728c-.307.198-.573.378-.777.517a3.273 3.273 0 0 0-1.516-.611c-1.507-.198-2.927.672-3.487 2.017a10.323 10.323 0 0 1-.695-1.078A10.92 10.92 0 0 1 .728 14.8a10.35 10.35 0 0 1-.2-1.212c-.164-1.653.103-3.258.629-4.754a12.95 12.95 0 0 1 1.087-2.288c.57-.968 1.248-1.872 2.069-2.656A11.013 11.013 0 0 1 11.808.797Zm-.147 3.257a3.838 3.838 0 0 0-3.82 3.82v2.36h-.94c-.751 0-1.377.625-1.377 1.377v3.8h1.46v-3.718h9.354v6.264H10.02v1.46h6.4c.751 0 1.377-.625 1.377-1.377v-6.43c0-.751-.626-1.377-1.377-1.377h-.94v-2.36a3.838 3.838 0 0 0-3.82-3.819zm0 1.46a2.371 2.371 0 0 1 2.36 2.36v2.36H9.3v-2.36a2.372 2.372 0 0 1 2.36-2.36zm10.141.392a1.253 1.253 0 0 1 1.296 1.434c-.049.319-.217.59-.453.78-.266.213-.61.318-.968.264a1.253 1.253 0 0 1-1.045-1.42 1.255 1.255 0 0 1 1.17-1.058zM5.384 17.425a2.02 2.02 0 0 1 1.917 1.298c.116.3.159.628.114.967a2.015 2.015 0 0 1-2.249 1.728 2.016 2.016 0 0 1-1.727-2.25 2.017 2.017 0 0 1 1.945-1.743z\"></path></svg></div>\n<div class=\"skillTracker\" tabindex=\"0\" aria-label=\"Skill path: 0 of 6 skills earned\"><div class=\"skillTracker__ring\" style=\"--skillTracker-pct:0%\"><span class=\"skillTracker__ringLabel\">0<!-- -->/<!-- -->6</span></div><div class=\"skillTracker__panel\"><div class=\"skillTracker__header\"><span class=\"skillTracker__title\">🎯 Skill path</span><span class=\"skillTracker__count\">0<!-- -->/<!-- -->6<!-- --> earned</span></div><div class=\"skillTracker__series\">Self-hosting OpenClaw</div><ul class=\"skillTracker__steps\"><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/\"><span class=\"skillTracker__dot\" data-state=\"locked\">1</span><span class=\"skillTracker__skill\" data-state=\"locked\">Deploy your own AI assistant</span></a></li><li><span class=\"skillTracker__step\"><span class=\"skillTracker__dot\" data-state=\"current\">2</span><span class=\"skillTracker__skill\" data-state=\"current\">Real HTTPS + auth</span></span></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/add-telegram-channel-openclaw/\"><span class=\"skillTracker__dot\" data-state=\"locked\">3</span><span class=\"skillTracker__skill\" data-state=\"locked\">Chat from Telegram</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/\"><span class=\"skillTracker__dot\" data-state=\"locked\">4</span><span class=\"skillTracker__skill\" data-state=\"locked\">Private mesh access</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/run-openclaw-on-wec-models/\"><span class=\"skillTracker__dot\" data-state=\"locked\">5</span><span class=\"skillTracker__skill\" data-state=\"locked\">Run it on WEC models</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/add-whatsapp-channel-openclaw/\"><span class=\"skillTracker__dot\" data-state=\"locked\">🏆</span><span class=\"skillTracker__skill\" data-state=\"locked\">Chat from WhatsApp</span></a></li></ul><div class=\"skillTracker__footer\">Finish this tutorial, then mark it complete below to earn the skill.</div></div></div>\n<p>En <a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/\">Artículo 1</a> hicimos funcionar OpenClaw —\npero solo a través de HTTP sin cifrar, con un workaround <code>allowInsecureAuth</code>. Aquí colocamos\n<a href=\"https://caddyserver.com/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Caddy</a> frente a él como un proxy inverso: verdadero HTTPS,\nautenticación emparejada por dispositivo, y los puertos del gateway cerrados para que el proxy sea la única\nforma de acceso. Cada comando y error a continuación proviene de la implementación real.</p>\n<!-- -->\n<div class=\"theme-admonition theme-admonition-info admonition_xJq3 alert alert--info\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z\"></path></svg></span>Reproducibilidad</div><div class=\"admonitionContent_BuS1\"><p>Continúa desde el Artículo 1 en la misma instancia WEC — <strong>Ubuntu 22.04.5 LTS</strong>, OpenClaw\n<strong>2026.6.8</strong>. Usamos <strong>Caddy 2</strong> (<code>caddy:2</code>). Debido a que esta máquina no tiene un dominio público,\nusamos la <strong>CA interna</strong> de Caddy (<code>tls internal</code>) para HTTPS autofirmado; la\nsección <a href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/#going-to-production\" class=\"\">Pasando a producción</a> muestra el cambio de una línea a certificados reales de\nLet's Encrypt una vez que tengas un dominio.</p></div></div>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"lo-que-construirás\">Lo que construirás<a href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/#lo-que-construir%C3%A1s\" class=\"hash-link\" aria-label=\"Enlace directo al Lo que construirás\" title=\"Enlace directo al Lo que construirás\" translate=\"no\">​</a></h2>\n<p>Caddy termina HTTPS y hace un proxy inverso al gateway de OpenClaw a través de la\nred interna de Docker. El gateway deja de publicar sus propios puertos — el único punto de entrada\nse convierte en Caddy en <code>:443</code>.</p>\n<!-- -->\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"requisitos-previos\">Requisitos previos<a href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/#requisitos-previos\" class=\"hash-link\" aria-label=\"Enlace directo al Requisitos previos\" title=\"Enlace directo al Requisitos previos\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\"><strong><a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/\">Artículo 1</a> completado</strong> — OpenClaw funcionando en <code>~/openclaw</code></li>\n<li class=\"\">Acceso a la terminal de la VM</li>\n<li class=\"\">Un nombre de host para el gateway. Con un dominio público, úsalo (y obtén certificados reales\nde Let's Encrypt). En una máquina privada, usamos <code>openclaw.local</code> + una entrada en hosts.</li>\n</ul>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-1--confirma-el-punto-de-partida\">Paso 1 — Confirma el punto de partida<a href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/#paso-1--confirma-el-punto-de-partida\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 1 — Confirma el punto de partida\" title=\"Enlace directo al Paso 1 — Confirma el punto de partida\" translate=\"no\">​</a></h2>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token builtin class-name\">cd</span><span class=\"token plain\"> ~/openclaw</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose </span><span class=\"token function\" style=\"color:#d73a49\">ps</span><br></div></code></pre></div></div>\n<p>Deberías ver <code>openclaw-gateway</code> <strong>Up (healthy)</strong>, publicando <code>18789/18790/3978</code>.\nEse es nuestro estado inicial: funcionando, pero solo HTTP.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"docker compose ps mostrando el gateway en funcionamiento\" src=\"data:image/png;base64,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\" width=\"567\" height=\"163\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-2--escribe-el-caddyfile\">Paso 2 — Escribe el Caddyfile<a href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/#paso-2--escribe-el-caddyfile\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 2 — Escribe el Caddyfile\" title=\"Enlace directo al Paso 2 — Escribe el Caddyfile\" translate=\"no\">​</a></h2>\n<p>La configuración de Caddy: servir HTTPS y hacer proxy a todo al gateway. <code>tls internal</code>\nusa la propia CA local de Caddy — no se necesita un dominio público.</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">cat</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> Caddyfile </span><span class=\"token operator\" style=\"color:#393A34\">&lt;&lt;</span><span class=\"token string\" style=\"color:#e3116c\">'EOF'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\"># Caddy selecciona su certificado por SNI (el nombre de host en el apretón de manos TLS).</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\"># Una IP desnuda no envía SNI, así que usamos un nombre de host. Con un dominio público, coloca tu</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\"># dominio aquí y elimina `tls internal` para Let's Encrypt automático.</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">openclaw.local {</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    tls internal</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    reverse_proxy openclaw-gateway:18789</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">}</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">EOF</span><br></div></code></pre></div></div>\n<p>El objetivo del proxy es <code>openclaw-gateway:18789</code> — Caddy accede al gateway por su\n<strong>nombre de servicio de Compose</strong> a través de la red compartida de Docker.</p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-3--agrega-caddy-a-compose\">Paso 3 — Agrega Caddy a Compose<a href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/#paso-3--agrega-caddy-a-compose\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 3 — Agrega Caddy a Compose\" title=\"Enlace directo al Paso 3 — Agrega Caddy a Compose\" translate=\"no\">​</a></h2>\n<p><code>docker compose</code> fusiona automáticamente un <code>docker-compose.override.yml</code>, así que agregamos un servicio <code>caddy</code>\nsin tocar el archivo de compose oficial de OpenClaw:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">cat</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> docker-compose.override.yml </span><span class=\"token operator\" style=\"color:#393A34\">&lt;&lt;</span><span class=\"token string\" style=\"color:#e3116c\">'EOF'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">services:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  caddy:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    image: caddy:2</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    restart: unless-stopped</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    ports:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      - \"80:80\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      - \"443:443\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    volumes:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      - ./Caddyfile:/etc/caddy/Caddyfile:ro</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      - caddy_data:/data</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      - caddy_config:/config</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">volumes:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  caddy_data:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  caddy_config:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">EOF</span><br></div></code></pre></div></div>\n<p><code>caddy_data</code> persiste la CA local + certificados a través de reinicios.</p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-4--inicia-caddy\">Paso 4 — Inicia Caddy<a href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/#paso-4--inicia-caddy\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 4 — Inicia Caddy\" title=\"Enlace directo al Paso 4 — Inicia Caddy\" translate=\"no\">​</a></h2>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose up </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose </span><span class=\"token function\" style=\"color:#d73a49\">ps</span><br></div></code></pre></div></div>\n<p>Caddy se inicia junto al gateway con <code>443</code> publicado, y genera su\nCA interna + un certificado en la primera ejecución.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Contenedor de Caddy en funcionamiento con 443 publicado\" src=\"https://wec.wiline.com/docs/es/assets/images/caddy-step4-up-d53a3b63c4e346428a8e733249b2049a.png\" width=\"654\" height=\"359\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<div class=\"theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z\"></path></svg></span>nota</div><div class=\"admonitionContent_BuS1\"><p><code>docker compose up -d</code> también inicia el ayudante de un solo uso <code>openclaw-cli</code>; su inactividad\naquí es inofensiva — lo invocamos con <code>docker compose run --rm</code> cuando es necesario.</p></div></div>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-5--verifica-https\">Paso 5 — Verifica HTTPS<a href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/#paso-5--verifica-https\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 5 — Verifica HTTPS\" title=\"Enlace directo al Paso 5 — Verifica HTTPS\" translate=\"no\">​</a></h2>\n<p>Accede al endpoint de salud del gateway <strong>a través de Caddy</strong>. <code>--resolve</code> hace que curl envíe\nel nombre de host (SNI) mientras apunta a la máquina:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-k</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--resolve</span><span class=\"token plain\"> openclaw.local:443:127.0.0.1 https://openclaw.local/healthz</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># {\"ok\":true,\"status\":\"live\"}</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"healthz devolviendo a través de HTTPS vía Caddy\" src=\"data:image/png;base64,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\" width=\"882\" height=\"41\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>¿Recibes un <code>tlsv1 alert internal error</code>?</div><div class=\"admonitionContent_BuS1\"><p>Eso sucede si usaste una <strong>IP desnuda</strong> como dirección del sitio — consulta\n<a href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/#1-tlsv1-alert-internal-error\" class=\"\">Solución de problemas #1</a>. Usa un nombre de host.</p></div></div>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-6--reasegura-el-gateway\">Paso 6 — Reasegura el gateway<a href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/#paso-6--reasegura-el-gateway\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 6 — Reasegura el gateway\" title=\"Enlace directo al Paso 6 — Reasegura el gateway\" translate=\"no\">​</a></h2>\n<p>Ahora el tráfico llega a través de <code>https://openclaw.local</code>. Dos cambios de configuración:</p>\n<ol>\n<li class=\"\">Agrega el <strong>origen</strong> HTTPS a <code>allowedOrigins</code> (la UI de Control rechaza orígenes desconocidos).</li>\n<li class=\"\">Desactiva <strong><code>allowInsecureAuth</code></strong> — HTTPS es un contexto seguro, por lo que el\nworkaround del Artículo 1 ya no es necesario.</li>\n</ol>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose run </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rm</span><span class=\"token plain\"> --no-deps </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--entrypoint</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">node</span><span class=\"token plain\"> openclaw-gateway </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  dist/index.js config </span><span class=\"token builtin class-name\">set</span><span class=\"token plain\"> --batch-json </span><span class=\"token string\" style=\"color:#e3116c\">'[{\"path\":\"gateway.controlUi.allowedOrigins\",\"value\":[\"https://openclaw.local\"]},{\"path\":\"gateway.controlUi.allowInsecureAuth\",\"value\":false}]'</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose restart openclaw-gateway</span><br></div></code></pre></div></div>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-7--empareja-tu-navegador\">Paso 7 — Empareja tu navegador<a href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/#paso-7--empareja-tu-navegador\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 7 — Empareja tu navegador\" title=\"Enlace directo al Paso 7 — Empareja tu navegador\" translate=\"no\">​</a></h2>\n<p>Abre <strong><code>https://openclaw.local/</code></strong> en tu navegador (en una máquina privada, primero mapea\n<code>10.80.4.212 openclaw.local</code> en el archivo hosts de tu máquina — <code>/etc/hosts</code> en\nmacOS/Linux, o <code>C:\\Windows\\System32\\drivers\\etc\\hosts</code> editado como Administrador\nen Windows).</p>\n<p><strong>Primero recibirás una advertencia de certificado</strong> — <em>\"Tu conexión no es privada\"</em>\n(<code>NET::ERR_CERT_AUTHORITY_INVALID</code>). Esto es esperado con <code>tls internal</code>: el\ncertificado está firmado por la <strong>CA local</strong> de Caddy, que tu sistema operativo no\nconfía. Haz clic en <strong>Avanzado → Proceder a openclaw.local (inseguro)</strong> para continuar. (Con un\ndominio real + Let's Encrypt, el certificado es públicamente confiable y esta advertencia\nnunca aparece — consulta <a href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/#going-to-production\" class=\"\">Pasando a producción</a>.)</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Advertencia de certificado del navegador — haz clic en Avanzado, luego Proceder\" src=\"https://wec.wiline.com/docs/es/assets/images/caddy-step7-cert-warning-f6015d4f4498bdd7f495c01e08e91cf5.png\" width=\"920\" height=\"744\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<p>Ahora la página se carga — pero con <code>allowInsecureAuth</code> desactivado, el gateway requiere\n<strong>emparejamiento de dispositivo</strong> antes de que este navegador pueda usar la UI de Control. Ese es el flujo seguro haciendo su trabajo. La pantalla muestra un id de solicitud; apruébalo en el host:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose run </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rm</span><span class=\"token plain\"> openclaw-cli devices approve </span><span class=\"token operator\" style=\"color:#393A34\">&lt;</span><span class=\"token plain\">request-id-from-the-screen</span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Pantalla que requiere emparejamiento de dispositivo\" src=\"https://wec.wiline.com/docs/es/assets/images/caddy-step7-pairing-95202988ec2160fdef5dcd2e33fce67f.png\" width=\"1100\" height=\"508\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<p>De vuelta en el navegador, haz clic en <strong>Conectar</strong> nuevamente — se empareja y carga el panel.</p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-8--cierra-el-acceso-directo-el-verdadero-endurecimiento\">Paso 8 — Cierra el acceso directo (el verdadero endurecimiento)<a href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/#paso-8--cierra-el-acceso-directo-el-verdadero-endurecimiento\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 8 — Cierra el acceso directo (el verdadero endurecimiento)\" title=\"Enlace directo al Paso 8 — Cierra el acceso directo (el verdadero endurecimiento)\" translate=\"no\">​</a></h2>\n<p>El gateway aún publica <code>18789</code> al host, por lo que <code>http://&lt;vm-ip&gt;:18789</code> es\n<em>todavía accesible en HTTP sin cifrar</em>, eludiendo a Caddy. Ciérralo: deja de publicar los\npuertos del gateway para que Caddy en <code>:443</code> sea la única puerta. Caddy aún puede acceder a él a través\nde la red interna.</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">cat</span><span class=\"token plain\"> </span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\"> docker-compose.override.yml </span><span class=\"token operator\" style=\"color:#393A34\">&lt;&lt;</span><span class=\"token string\" style=\"color:#e3116c\">'EOF'</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">services:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  openclaw-gateway:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    # Deja de publicar los puertos del host del gateway — Caddy accede a él internamente, así que el</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    # único punto de entrada es HTTPS en :443.</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    ports: !reset []</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  caddy:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    image: caddy:2</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    restart: unless-stopped</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    ports:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      - \"80:80\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      - \"443:443\"</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">    volumes:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      - ./Caddyfile:/etc/caddy/Caddyfile:ro</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      - caddy_data:/data</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">      - caddy_config:/config</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">volumes:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  caddy_data:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">  caddy_config:</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token string\" style=\"color:#e3116c\">EOF</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose up </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><br></div></code></pre></div></div>\n<p><code>ports: !reset []</code> limpia los puertos que el compose base publicó.</p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-9--prueba-que-está-bloqueado\">Paso 9 — Prueba que está bloqueado<a href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/#paso-9--prueba-que-est%C3%A1-bloqueado\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 9 — Prueba que está bloqueado\" title=\"Enlace directo al Paso 9 — Prueba que está bloqueado\" translate=\"no\">​</a></h2>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-s</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-m</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">5</span><span class=\"token plain\"> http://</span><span class=\"token operator\" style=\"color:#393A34\">&lt;</span><span class=\"token plain\">vm-ip</span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><span class=\"token plain\">:18789/healthz </span><span class=\"token punctuation\" style=\"color:#393A34\">;</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">echo</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\" &lt;- HTTP directo (debería FALLAR)\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-sk</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--resolve</span><span class=\"token plain\"> openclaw.local:443:127.0.0.1 https://openclaw.local/healthz </span><span class=\"token punctuation\" style=\"color:#393A34\">;</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">echo</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\" &lt;- a través de Caddy (debería funcionar)\"</span><br></div></code></pre></div></div>\n<p>El primero no devuelve nada (conexión rechazada) — la puerta insegura ha desaparecido. El\nsegundo aún devuelve <code>{\"ok\":true,\"status\":\"live\"}</code>.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Antes/después: puerto directo rechazado, HTTPS funciona\" src=\"data:image/png;base64,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\" width=\"1089\" height=\"107\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<p>Tu OpenClaw ahora es accesible <strong>solo</strong> a través de HTTPS, emparejado por dispositivo.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"UI de Control de OpenClaw sobre HTTPS\" src=\"https://wec.wiline.com/docs/es/assets/images/caddy-control-ui-https-814c25fc8dd38568d2226856190421ba.png\" width=\"1100\" height=\"663\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Habilidad desbloqueada 🏅</div><div class=\"admonitionContent_BuS1\"><p>Ahora puedes <strong>poner verdadero HTTPS y autenticación frente a cualquier servicio autoalojado</strong> — y probar que\nla puerta trasera está cerrada.</p></div></div>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"solución-de-problemas-errores-reales\">Solución de problemas (errores reales)<a href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/#soluci%C3%B3n-de-problemas-errores-reales\" class=\"hash-link\" aria-label=\"Enlace directo al Solución de problemas (errores reales)\" title=\"Enlace directo al Solución de problemas (errores reales)\" translate=\"no\">​</a></h2>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"1-tlsv1-alert-internal-error\">1. <code>tlsv1 alert internal error</code><a href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/#1-tlsv1-alert-internal-error\" class=\"hash-link\" aria-label=\"Enlace directo al 1-tlsv1-alert-internal-error\" title=\"Enlace directo al 1-tlsv1-alert-internal-error\" translate=\"no\">​</a></h3>\n<p>Ocurre cuando la dirección del sitio en el Caddyfile era una IP desnuda (<code>https://10.80.4.212</code>):</p>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">curl: (35) error:0A000438:SSL routines::tlsv1 alert internal error</span><br></div></code></pre></div></div>\n<p>Los registros de Caddy mostraron que el certificado se obtuvo correctamente. <strong>Causa:</strong> TLS selecciona un certificado\npor <strong>SNI</strong> (el nombre de host en el apretón de manos), pero las conexiones a una IP desnuda no envían SNI — así que Caddy no puede\ncoincidir un certificado y aborta. <strong>Solución:</strong> usa una dirección de sitio con nombre de host\n(Paso 2). Un lector con un dominio real nunca enfrenta esto — el dominio <em>es</em> el SNI.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"2-se-requiere-emparejamiento-de-dispositivo\">2. Se requiere emparejamiento de dispositivo<a href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/#2-se-requiere-emparejamiento-de-dispositivo\" class=\"hash-link\" aria-label=\"Enlace directo al 2. Se requiere emparejamiento de dispositivo\" title=\"Enlace directo al 2. Se requiere emparejamiento de dispositivo\" translate=\"no\">​</a></h3>\n<p>En la primera conexión HTTPS después de desactivar <code>allowInsecureAuth</code>:</p>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Se requiere emparejamiento de dispositivo — Este navegador necesita una aprobación única del host del Gateway</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">antes de que pueda usar la UI de Control.</span><br></div></code></pre></div></div>\n<p><strong>Causa:</strong> este es el flujo seguro <em>correcto</em> — con la autenticación insegura desactivada, cada nuevo\nnavegador debe ser aprobado. <strong>Solución:</strong> <code>docker compose run --rm openclaw-cli devices approve &lt;request-id&gt;</code>. (El CLI puede registrar <code>scope upgrade pending approval … using local fallback</code> pero aún completa la aprobación.)</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"3-neterr_cert_authority_invalid\">3. <code>NET::ERR_CERT_AUTHORITY_INVALID</code><a href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/#3-neterr_cert_authority_invalid\" class=\"hash-link\" aria-label=\"Enlace directo al 3-neterr_cert_authority_invalid\" title=\"Enlace directo al 3-neterr_cert_authority_invalid\" translate=\"no\">​</a></h3>\n<p>Esperado con <code>tls internal</code> — el certificado está firmado por la CA local de Caddy, que tu\nsistema operativo no confía. Haz clic para continuar en una máquina privada. Con un dominio público + Let's\nEncrypt (a continuación), el certificado es confiable y no hay advertencia.</p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"pasando-a-producción\">Pasando a producción<a href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/#pasando-a-producci%C3%B3n\" class=\"hash-link\" aria-label=\"Enlace directo al Pasando a producción\" title=\"Enlace directo al Pasando a producción\" translate=\"no\">​</a></h2>\n<p>Con un dominio real, intercambia dos líneas en el Caddyfile — apúntalo a tu dominio y\nelimina <code>tls internal</code>:</p>\n<div class=\"language-diff codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-diff codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">- openclaw.local {</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">-     tls internal</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">+ openclaw.example.com {</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">      reverse_proxy openclaw-gateway:18789</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  }</span><br></div></code></pre></div></div>\n<p>Luego apunta el <strong>registro A</strong> del dominio a la IP pública de la VM y abre <strong>80 + 443</strong>\na Internet. Caddy automáticamente provisiona y renueva un certificado de <strong>Let's Encrypt</strong>\n— confiable, sin advertencias, sin <code>--resolve</code> o <code>/etc/hosts</code> necesarios. Actualiza\n<code>allowedOrigins</code> a <code>https://openclaw.example.com</code>.</p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"notas-de-seguridad\">Notas de seguridad<a href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/#notas-de-seguridad\" class=\"hash-link\" aria-label=\"Enlace directo al Notas de seguridad\" title=\"Enlace directo al Notas de seguridad\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\"><strong>Único punto de entrada</strong> — solo el <code>:443</code> de Caddy está expuesto; el gateway no tiene puertos de host\n(Paso 8).</li>\n<li class=\"\"><strong>Emparejamiento de dispositivo</strong> — cada navegador necesita aprobación única (<code>openclaw devices</code>).</li>\n<li class=\"\"><strong>Lista de orígenes permitidos</strong> — la UI de Control solo acepta <code>https://openclaw.local</code>.</li>\n<li class=\"\"><strong>Rotar secretos</strong> — regenera <code>OPENCLAW_GATEWAY_TOKEN</code> si alguna vez ha sido\nmostrado (capturas de pantalla, comparticiones de pantalla).</li>\n</ul>\n<hr>\n<div class=\"skillComplete\"><span class=\"skillComplete__medal\" aria-hidden=\"true\">🎯</span><div class=\"skillComplete__text\"><div class=\"skillComplete__title\">Finished this tutorial?</div><div class=\"skillComplete__sub\">Mark it complete to earn <strong>Real HTTPS + auth</strong> on your skill path.</div></div><button type=\"button\" class=\"skillComplete__btn\">Mark as complete ✓</button></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"qué-sigue\">¿Qué sigue?<a href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/#qu%C3%A9-sigue\" class=\"hash-link\" aria-label=\"Enlace directo al ¿Qué sigue?\" title=\"Enlace directo al ¿Qué sigue?\" translate=\"no\">​</a></h2>\n<p>Has terminado <strong>Parte 2</strong> de la serie <strong>Autoalojamiento de OpenClaw</strong>. A continuación:</p>\n<ul>\n<li class=\"\"><strong>Parte 3 — <a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/add-telegram-channel-openclaw/\">Agregar un canal de Telegram</a></strong> <em>(siguiente)</em> — habla con tu agente ahora asegurado desde tu teléfono.</li>\n<li class=\"\"><strong>Parte 4 — <a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/\">Hacer OpenClaw privado con una VPN de malla NetBird</a></strong> — colócalo en una malla privada y cierra los puertos públicos.</li>\n</ul>\n<p>Los archivos complementarios (<code>Caddyfile</code>, <code>docker-compose.override.yml</code>) se encuentran en el repositorio de manifiestos de WiLine <em>(enlace que vendrá con el repositorio)</em>.</p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"desmontaje\">Desmontaje<a href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/#desmontaje\" class=\"hash-link\" aria-label=\"Enlace directo al Desmontaje\" title=\"Enlace directo al Desmontaje\" translate=\"no\">​</a></h2>\n<p>Para eliminar solo Caddy y reabrir los puertos directos, elimina <code>docker-compose.override.yml</code>\ny <code>Caddyfile</code>, luego <code>docker compose up -d</code>. Para detener todo:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose down</span><br></div></code></pre></div></div>",
            "url": "https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/",
            "title": "Asegurar OpenClaw con un proxy inverso Caddy + HTTPS",
            "summary": "Coloca Caddy frente a OpenClaw para un verdadero HTTPS y autenticación emparejada por dispositivo, luego cierra los puertos del gateway para que el proxy sea la única forma de acceso — problemas y todo.",
            "date_modified": "2026-06-23T00:00:00.000Z",
            "author": {
                "name": "Rafael Fernandes",
                "url": "https://www.linkedin.com/in/rafaelmacariofernandes/"
            },
            "tags": [
                "ai",
                "autoalojamiento",
                "docker",
                "openclaw",
                "caddy",
                "https",
                "seguridad"
            ]
        },
        {
            "id": "https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/",
            "content_html": "<div class=\"tutorialHero\"><img class=\"tutorialHero__brand\" src=\"https://wec.wiline.com/docs/es/assets/images/openclaw-wordmark-003352a1a7f02afc3bd877ae6f2dc175.png\" alt=\"OpenClaw\"><span class=\"tutorialHero__plus\">+</span><svg viewBox=\"0 0 24 24\" fill=\"#2496ED\" aria-label=\"Docker\" class=\"tutorialHero__docker\"><path d=\"M13.983 11.078h2.119a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.119a.185.185 0 00-.185.185v1.888c0 .102.083.185.185.185m-2.954-5.43h2.118a.186.186 0 00.186-.186V3.574a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m0 2.716h2.118a.187.187 0 00.186-.186V6.29a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.887c0 .102.082.185.185.186m-2.93 0h2.12a.186.186 0 00.184-.186V6.29a.185.185 0 00-.185-.185H8.1a.185.185 0 00-.185.185v1.887c0 .102.083.185.185.186m-2.964 0h2.119a.186.186 0 00.185-.186V6.29a.185.185 0 00-.185-.185H5.136a.186.186 0 00-.186.185v1.887c0 .102.084.185.186.186m5.893 2.715h2.118a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m-2.93 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.083.185.185.185m-2.964 0h2.119a.185.185 0 00.185-.185V9.006a.185.185 0 00-.184-.186h-2.12a.186.186 0 00-.186.186v1.887c0 .102.084.185.186.185m-2.92 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.082.185.185.185M23.763 9.89c-.065-.051-.672-.51-1.954-.51-.338.001-.676.03-1.01.087-.248-1.7-1.653-2.53-1.716-2.566l-.344-.199-.226.327c-.284.438-.49.922-.612 1.43-.23.97-.09 1.882.403 2.661-.595.332-1.55.413-1.744.42H.751a.751.751 0 00-.75.748 11.376 11.376 0 00.692 4.062c.545 1.428 1.355 2.48 2.41 3.124 1.18.723 3.1 1.137 5.275 1.137.983.003 1.963-.086 2.93-.266a12.248 12.248 0 003.823-1.389c.98-.567 1.86-1.288 2.61-2.136 1.252-1.418 1.998-2.997 2.553-4.4h.221c1.372 0 2.215-.549 2.68-1.009.309-.293.55-.65.707-1.046l.098-.288Z\"></path></svg><span class=\"tutorialHero__plus\">+</span><svg viewBox=\"0 0 24 24\" fill=\"#0a0a0a\" aria-label=\"OpenAI\" class=\"tutorialHero__openai\"><path d=\"M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997z\"></path></svg></div>\n<div class=\"skillTracker\" tabindex=\"0\" aria-label=\"Skill path: 0 of 6 skills earned\"><div class=\"skillTracker__ring\" style=\"--skillTracker-pct:0%\"><span class=\"skillTracker__ringLabel\">0<!-- -->/<!-- -->6</span></div><div class=\"skillTracker__panel\"><div class=\"skillTracker__header\"><span class=\"skillTracker__title\">🎯 Skill path</span><span class=\"skillTracker__count\">0<!-- -->/<!-- -->6<!-- --> earned</span></div><div class=\"skillTracker__series\">Self-hosting OpenClaw</div><ul class=\"skillTracker__steps\"><li><span class=\"skillTracker__step\"><span class=\"skillTracker__dot\" data-state=\"current\">1</span><span class=\"skillTracker__skill\" data-state=\"current\">Deploy your own AI assistant</span></span></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/\"><span class=\"skillTracker__dot\" data-state=\"locked\">2</span><span class=\"skillTracker__skill\" data-state=\"locked\">Real HTTPS + auth</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/add-telegram-channel-openclaw/\"><span class=\"skillTracker__dot\" data-state=\"locked\">3</span><span class=\"skillTracker__skill\" data-state=\"locked\">Chat from Telegram</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/\"><span class=\"skillTracker__dot\" data-state=\"locked\">4</span><span class=\"skillTracker__skill\" data-state=\"locked\">Private mesh access</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/run-openclaw-on-wec-models/\"><span class=\"skillTracker__dot\" data-state=\"locked\">5</span><span class=\"skillTracker__skill\" data-state=\"locked\">Run it on WEC models</span></a></li><li><a class=\"skillTracker__step\" href=\"https://wec.wiline.com/docs/es/tutorials/add-whatsapp-channel-openclaw/\"><span class=\"skillTracker__dot\" data-state=\"locked\">🏆</span><span class=\"skillTracker__skill\" data-state=\"locked\">Chat from WhatsApp</span></a></li></ul><div class=\"skillTracker__footer\">Finish this tutorial, then mark it complete below to earn the skill.</div></div></div>\n<p>Tu asistente de IA no tiene que vivir en la nube de otra persona.</p>\n<p>Despliega un agente de IA <a href=\"https://openclaw.ai/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">OpenClaw</a> autoalojado en una instancia WEC con\nDocker Compose — desde iniciar la VM hasta un agente que realmente responde, usando\ntu propia clave de API de modelo. Cada comando, versión y error a continuación fue capturado\nde un despliegue real en una instancia WEC.</p>\n<!-- -->\n<div class=\"theme-admonition theme-admonition-info admonition_xJq3 alert alert--info\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z\"></path></svg></span>Reproducibilidad</div><div class=\"admonitionContent_BuS1\"><p>Traes tu propia clave de API de modelo (OpenAI en esta guía); WiLine maneja el\nalojamiento. Esto se reprodujo en una instancia WEC — <strong>Ubuntu 22.04.5 LTS, 8 vCPU,\n15 GiB RAM, 25 GB de disco libre</strong>. Versión de OpenClaw <strong>2026.6.8</strong>, imagen\n<code>ghcr.io/openclaw/openclaw:latest</code>.</p></div></div>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"lo-que-construirás\">Lo que construirás<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/#lo-que-construir%C3%A1s\" class=\"hash-link\" aria-label=\"Enlace directo al Lo que construirás\" title=\"Enlace directo al Lo que construirás\" translate=\"no\">​</a></h2>\n<p>Un único contenedor <strong>gateway</strong> de OpenClaw, configurado con tu proveedor de modelo,\naccesible a través de su interfaz de Control UI web y CLI.</p>\n<!-- -->\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"requisitos-previos\">Requisitos previos<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/#requisitos-previos\" class=\"hash-link\" aria-label=\"Enlace directo al Requisitos previos\" title=\"Enlace directo al Requisitos previos\" translate=\"no\">​</a></h2>\n<ul>\n<li class=\"\">Una cuenta de WiLine Edge Cloud</li>\n<li class=\"\">Una clave de API de modelo — esta guía utiliza <strong>OpenAI</strong>; Anthropic y modelos locales también funcionan</li>\n<li class=\"\">Un par de claves SSH en tu máquina (<code>~/.ssh/id_ed25519</code> o similar)</li>\n<li class=\"\">Aproximadamente 20 minutos</li>\n</ul>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-1--provisión-de-la-vm-en-wiline\">Paso 1 — Provisión de la VM en WiLine<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/#paso-1--provisi%C3%B3n-de-la-vm-en-wiline\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 1 — Provisión de la VM en WiLine\" title=\"Enlace directo al Paso 1 — Provisión de la VM en WiLine\" translate=\"no\">​</a></h2>\n<p>Inicia una VM de WiLine que cumpla con estos requisitos:</p>\n<table><thead><tr><th>Requisito</th><th>Valor</th></tr></thead><tbody><tr><td>SO</td><td>Ubuntu 22.04 LTS (o 24.04 LTS)</td></tr><tr><td>Cómputo</td><td>≥ 2 vCPU / 4 GB RAM — OpenClaw necesita ≥ 2 GB; la primera ejecución puede ser OOM-killed (salida 137) en hosts de 1 GB</td></tr><tr><td>Disco</td><td>30 GB NVMe — 10 GB se llena rápido una vez que las imágenes de Docker y los registros se almacenan</td></tr><tr><td>Acceso</td><td>Tu <strong>clave pública SSH</strong> añadida en el momento del despliegue, para que puedas iniciar sesión sin contraseña</td></tr></tbody></table>\n<p>La guía del portal ya está documentada — sigue estos pasos y vuelve:</p>\n<ul>\n<li class=\"\"><strong><a class=\"\" href=\"https://wec.wiline.com/docs/es/cloud_portal/platform/compute/instances/compute_instance/\">Desplegar una Máquina Virtual →</a></strong></li>\n<li class=\"\"><strong><a class=\"\" href=\"https://wec.wiline.com/docs/es/cloud_portal/platform/compute/ssh_keys/\">Configurar una Clave SSH →</a></strong></li>\n</ul>\n<p>Una vez que esté en funcionamiento, inicia sesión por SSH:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">ssh</span><span class=\"token plain\"> ubuntu@</span><span class=\"token operator\" style=\"color:#393A34\">&lt;</span><span class=\"token plain\">tu-ip-vm</span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><br></div></code></pre></div></div>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-2--verifica-la-máquina\">Paso 2 — Verifica la máquina<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/#paso-2--verifica-la-m%C3%A1quina\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 2 — Verifica la máquina\" title=\"Enlace directo al Paso 2 — Verifica la máquina\" translate=\"no\">​</a></h2>\n<p>Antes de instalar cualquier cosa, confirma con qué estás trabajando — la versión del SO y\nlos recursos disponibles. Toma nota de estos números; son útiles cuando compares\nel rendimiento o abras un ticket de soporte.</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">lsb_release </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-a</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">nproc</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">free</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-h</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">df</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-h</span><span class=\"token plain\"> /</span><br></div></code></pre></div></div>\n<p>En nuestra instancia WEC: <code>Ubuntu 22.04.5 LTS</code>, <code>8</code> vCPU, ~<code>15 GiB</code> RAM, <code>25 GB</code> de disco libre.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Terminal mostrando la versión del SO, CPU, RAM y disco\" src=\"https://wec.wiline.com/docs/es/assets/images/step2-box-specs-c821280774bf4f5ec9205092f6646004.png\" width=\"584\" height=\"316\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-3--instalar-docker--compose\">Paso 3 — Instalar Docker + Compose<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/#paso-3--instalar-docker--compose\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 3 — Instalar Docker + Compose\" title=\"Enlace directo al Paso 3 — Instalar Docker + Compose\" translate=\"no\">​</a></h2>\n<p>Verifica si Docker ya está presente:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--version</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose version</span><br></div></code></pre></div></div>\n<p>Si no está instalado, sigue la guía oficial de Docker para tu distribución —\n<strong><a href=\"https://docs.docker.com/engine/install/ubuntu/\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">Instalar Docker Engine en Ubuntu</a></strong>.\nEl camino más rápido es el\n<a href=\"https://docs.docker.com/engine/install/#install-using-the-convenience-script\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"\">script de conveniencia de Docker</a>,\nseguido de añadir tu usuario al grupo <code>docker</code> para que puedas ejecutarlo sin\n<code>sudo</code>:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-fsSL</span><span class=\"token plain\"> https://get.docker.com </span><span class=\"token operator\" style=\"color:#393A34\">|</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">sh</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">sudo</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">usermod</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-aG</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> </span><span class=\"token environment constant\" style=\"color:#36acaa\">$USER</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">newgrp </span><span class=\"token function\" style=\"color:#d73a49\">docker</span><br></div></code></pre></div></div>\n<p>Necesitas <strong>Docker Compose v2</strong> (el subcomando <code>docker compose</code>, no el binario legado\n<code>docker-compose</code>).</p>\n<div class=\"theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 14 16\"><path fill-rule=\"evenodd\" d=\"M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z\"></path></svg></span>nota</div><div class=\"admonitionContent_BuS1\"><p>Si Docker ya está presente, el script <code>get.docker.com</code> lo detecta y advierte\nen lugar de reinstalar — no lo fuerces en una máquina con contenedores en ejecución. En una\nVM limpia se instala desde cero. Este despliegue se realizó en un host donde Docker\n<strong>29.1.3 / Compose v5.0.2</strong> ya estaba instalado.</p></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"docker --version y salida de docker compose version\" src=\"data:image/png;base64,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\" width=\"582\" height=\"234\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-4--obtener-el-archivo-compose\">Paso 4 — Obtener el archivo Compose<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/#paso-4--obtener-el-archivo-compose\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 4 — Obtener el archivo Compose\" title=\"Enlace directo al Paso 4 — Obtener el archivo Compose\" translate=\"no\">​</a></h2>\n<p>Crea un directorio de proyecto y descarga el <code>docker-compose.yml</code> oficial de OpenClaw:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">mkdir</span><span class=\"token plain\"> openclaw </span><span class=\"token operator\" style=\"color:#393A34\">&amp;&amp;</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">cd</span><span class=\"token plain\"> openclaw</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-fsSL</span><span class=\"token plain\"> https://raw.githubusercontent.com/openclaw/openclaw/main/docker-compose.yml </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-o</span><span class=\"token plain\"> docker-compose.yml</span><br></div></code></pre></div></div>\n<p>El archivo define dos servicios — <code>openclaw-gateway</code> (el gateway de agente de larga duración)\ny <code>openclaw-cli</code> (una CLI de un solo uso que comparte la red del gateway).\nPor defecto, <strong>se construye desde el código fuente</strong>; en su lugar, lo apuntaremos a la imagen preconstruida.</p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-5--configurar-env\">Paso 5 — Configurar <code>.env</code><a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/#paso-5--configurar-env\" class=\"hash-link\" aria-label=\"Enlace directo al paso-5--configurar-env\" title=\"Enlace directo al paso-5--configurar-env\" translate=\"no\">​</a></h2>\n<p>Crea un <code>.env</code> junto al archivo compose. Fija la imagen preconstruida y establece un\ntoken de panel:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token assign-left variable\" style=\"color:#36acaa\">OPENCLAW_IMAGE</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token plain\">ghcr.io/openclaw/openclaw:latest</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token assign-left variable\" style=\"color:#36acaa\">OPENCLAW_GATEWAY_TOKEN</span><span class=\"token operator\" style=\"color:#393A34\">=</span><span class=\"token operator\" style=\"color:#393A34\">&lt;</span><span class=\"token plain\">pega la salida de: openssl rand </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-hex</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">3</span><span class=\"token operator file-descriptor important\" style=\"color:#393A34\">2</span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><br></div></code></pre></div></div>\n<div class=\"theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z\"></path></svg></span>Mantén <code>.env</code> fuera de git</div><div class=\"admonitionContent_BuS1\"><p>Contiene tu token de gateway (y cualquier clave). Agrégalo a <code>.gitignore</code>. En su lugar, compromete un\n<code>.env.example</code> con valores de marcador de posición.</p></div></div>\n<p>Descarga la imagen:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose pull</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"docker compose pull completando con la línea Pulled\" src=\"data:image/png;base64,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\" width=\"583\" height=\"176\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-6--precrear-el-directorio-de-configuración\">Paso 6 — Precrear el directorio de configuración<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/#paso-6--precrear-el-directorio-de-configuraci%C3%B3n\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 6 — Precrear el directorio de configuración\" title=\"Enlace directo al Paso 6 — Precrear el directorio de configuración\" translate=\"no\">​</a></h2>\n<p>OpenClaw monta <code>~/.openclaw</code> en el contenedor. Créalo <strong>como tu propio\nusuario primero</strong> — de lo contrario, Docker lo crea como <code>root</code> y el contenedor (que\nse ejecuta como usuario <code>node</code>, uid 1000) no puede escribir en él:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">mkdir</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-p</span><span class=\"token plain\"> ~/.openclaw ~/.openclaw-auth-profile-secrets</span><br></div></code></pre></div></div>\n<p>Si omites esto, recibirás un error de permisos en la primera ejecución — consulta\n<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/#troubleshooting-real-errors\" class=\"\">Solución de problemas</a>.</p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-7--incorporación\">Paso 7 — Incorporación<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/#paso-7--incorporaci%C3%B3n\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 7 — Incorporación\" title=\"Enlace directo al Paso 7 — Incorporación\" translate=\"no\">​</a></h2>\n<p>Aquí es donde traes tu propio modelo — la incorporación solicita tu proveedor y\nclave de API. Esta guía utiliza <strong>OpenAI</strong>, pero Anthropic y modelos locales funcionan de la misma manera.</p>\n<p>Ejecuta la incorporación de OpenClaw a través del contenedor gateway. Es interactivo — solicita\ntu proveedor y clave de API y escribe la configuración:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose run </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rm</span><span class=\"token plain\"> --no-deps </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--entrypoint</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">node</span><span class=\"token plain\"> openclaw-gateway </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  dist/index.js onboard </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--mode</span><span class=\"token plain\"> </span><span class=\"token builtin class-name\">local</span><span class=\"token plain\"> --no-install-daemon</span><br></div></code></pre></div></div>\n<p>Sigue los pasos:</p>\n<ul>\n<li class=\"\"><strong>Continúa</strong> más allá del aviso de seguridad de uso personal → <strong>Sí</strong></li>\n<li class=\"\"><strong>Modo de configuración</strong> → <strong>QuickStart</strong></li>\n<li class=\"\"><strong>Proveedor</strong> → <strong>OpenAI</strong> (o tu proveedor), luego pega tu <strong>clave de API</strong></li>\n<li class=\"\"><strong>Modelo predeterminado</strong> → mantén el sugerido (<code>openai/gpt-5.5</code>)</li>\n<li class=\"\"><strong>Canal</strong> → busca <code>skip</code> → <strong>Saltar por ahora</strong> (los canales de mensajería son una guía posterior)</li>\n<li class=\"\"><strong>Habilidades / Hooks</strong> → <strong>No / Saltar por ahora</strong> (configura más tarde con\n<code>openclaw configure</code>)</li>\n<li class=\"\"><strong>Incubar tu agente</strong> → <strong>Incubar más tarde</strong> (iniciaremos el gateway como un servicio a continuación)</li>\n</ul>\n<p>Termina con <code>Incorporación completa.</code></p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Incorporación — seleccionando el proveedor OpenAI\" src=\"data:image/png;base64,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\" width=\"626\" height=\"391\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Incorporación — &amp;quot;Modelo configurado: openai/gpt-5.5&amp;quot;\" src=\"data:image/png;base64,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\" width=\"627\" height=\"392\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-8--aplicar-la-configuración-del-gateway-y-comenzar\">Paso 8 — Aplicar la configuración del gateway y comenzar<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/#paso-8--aplicar-la-configuraci%C3%B3n-del-gateway-y-comenzar\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 8 — Aplicar la configuración del gateway y comenzar\" title=\"Enlace directo al Paso 8 — Aplicar la configuración del gateway y comenzar\" translate=\"no\">​</a></h2>\n<p>La incorporación escribió tu proveedor y modelo. Ahora establece las opciones de tiempo de ejecución del gateway —\nmodo de ejecución, enlace de red y los orígenes de Control UI que aceptará — y luego inícialo\ncomo un servicio en segundo plano.</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose run </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rm</span><span class=\"token plain\"> --no-deps </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--entrypoint</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">node</span><span class=\"token plain\"> openclaw-gateway </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  dist/index.js config </span><span class=\"token builtin class-name\">set</span><span class=\"token plain\"> --batch-json </span><span class=\"token string\" style=\"color:#e3116c\">'[{\"path\":\"gateway.mode\",\"value\":\"local\"},{\"path\":\"gateway.bind\",\"value\":\"lan\"},{\"path\":\"gateway.controlUi.allowedOrigins\",\"value\":[\"http://localhost:18789\",\"http://127.0.0.1:18789\"]}]'</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose up </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-d</span><span class=\"token plain\"> openclaw-gateway</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"docker compose up -d creando el contenedor gateway\" src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAnUAAACpCAMAAAB6SjsbAAAApVBMVEURIECU3FWvrq+O01MPHT14d3fc3Nw7OjoiLksAAAACev4ZKUIwMC5GRURHT2Zpa3QbGxp7foiUlZqIipFeoOmAvVAwOlTMzcy7vMBbYnWlpqqenp4uRz1klEpzq05VVVQQfMpgX19QeUYFBQVKhdRGgxL/X1cpyED+vC5GeLMlRnQ/X0UaM10rV5AyWiA+Z6Cmwv/m7f8vrj3cV1KYXz7bpDAmJSTYPsBzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAXBklEQVR42u2dDX+iOBPAMRZ0n4gsoIKgUqy9u1ptb+/27vt/tGcmbySIrd2e2t3O/HZV8jIZyJ/JC2nwvn4cGdmS+6NRmIWjJIODOhs0AQwCing0ymr4kSSYNhqV9ajMWJ0zkSrGJOkID0bhALILjTl8xEwoGY3KFHOXowpUiyAdoD6kEVFey9gRK0YjP5fBYVaNRoMs1UcQHcHHIIukVSkTASIZli+MbKcM8yyLB6ogY4XUbYkITNGitBj5eDCAIuSZ6YuRjqJcHsO3sSSPc/gBJwza/SrFEhrLtcYmY5iXunQ0mcnidXxjAXyjdjgndZ1jecEgMqyEBpFnEDrVWOpqFOJ9/fdjUpeycJCA+UmRhiFLrAC8HiWcb57A1QEsBlgXMaTA85SXllWDGAJ9FRlmPiTN82hQZ76iLsqiMBKVU4ZhwawAi7pUkCoyFBZ1o5KFIRhmcVFlYF2m7oWYDUIGPxIwGct3qBMp83gUQeSgucmUFSJNzJrrIKoQsAHTI7ARkpX5ACypRjXcbPpiDOBi4FWC1Fo/3K9gYCnujZpBRsdyrdHKGObJqDFZmaDjdXqXOnWdjXl5hZlFnkReqySXtVZmSVjl+sJ6WN0I3t3dl7svH0ru7tCgO/0Dv0SA+6FjdSb5oQObb6XJpLHjWomtC2FluLMjnELdIkz0XZPuzrHOGONqsQo/OKNGUcvYDlObwu2TPSznUJF1Pe8OTrWVztRNZ6LmzHStfbn7N6z+VSgL6r7effk6qKLIJyE5l0RRFX69+/qv8XV3X+s6BBmQkJxNwrCq668CO6TublATcSQXAa8eIHYeQlcRcyQX4q5C7DxsXgk6kothB40sUPeFPB3JRbG7+9cjV0dycWfn3Y0IOpJzDByaz3YMUGdFfP/7t9/+/u4m+f7PH3/8893NvP7r99//Wp/X6Ng/U282NN3a1o9TxY+779PKT9sRVRydpvPlXk4dV1e+bCedA0zK1eITngSFMEEHhvt+JWNquwruvKZbFwJzKH9bVyAE5lD+scIWwBzKX4tzUseKY+cWuZe5ZEXaWbd48m2JGSuwXsKEsbK2f0QFO7HCSlZ1W8wOInwWn6IxKhmDx3twMqxTd8rq9162S3i3CJ4zVPgJ1sKcsB+Gfh3BRY38Q+pMt05BZ2OnoXOwU9ABdtegrmaJzWBRwJnFLO1IydgBdT4QWrAIWStTVoTNjxSYeR91NSs7yjuFuoqxtIRTCEvWTV3Bwp+AOizeuITIr/2ohrqBAGTPpq7+4n3R6b7/ZsQ0st//MGLC1r8bOUMjW5UAUFzWcPn8sgQOEqjLuowHaemXRRzWBfimyi+BnLKE//BMuQ6rCIiJyjgtEji7MC1wxQBUYRlBAr9ANZg9geqrBxErgRAALYFM5gdLU0NdLMutQGPCkgrANCigDQ11Qmnol6z0hb8qKyxb2VBCQ9xQh7qKUEejmtRoxZsGPGVVM3C4EBjVbSpLkSWJZJEFtuR1Ys6rwCY/LlEzUqcMwmvkC8Piy2EX+ZVGCzxcBI/B0IVDS2ufUmRR93dD3d86/p+Gun8OXN1ZnJ24wgmrsb0q0f3gfR5BtWAjxFhUJ+CuoCXysZ4AmsqHNpNVaQI1zEpobRG3BEAJwXulEbiRAnJGmB0wgCyYL/TROaJW86MexIY65LKC6gONSSG0liaGlUXjj4RSSA3+s5aGAaVQLtqQwk+LOjSz1NGRVKzVJNiAJiyCHjZSV7WddARaYtDOIF0pLEjwdgH8fX1ZwFdjfIXUKYPkRSshZ3QNZwc/q1D3cY5T95slOv4PS3TY75aclTrplhrqIsGIaGE1dX45KJEvcHZAEnhFSFWjhhjcAVZehXXrQxasIawq0fJWMdJQiypRP5RO7ApXsOQoRF6wODQBORWdZKjbWHglmU4qhfFJKN0mGFZVYSW8VlFVTGqROYV1OjqV5ms1wsFJV4s/wzTWfjWtw6gS3BTiPFJRJGoGSyq0TV0W1DaI0lr4OmlQCCXCDQk3wCUnKSIFGvo66OZFsv/2E1EnOjwWdZWoWIe6GKhEj1JFhfQrQJHxL5K6YiA+RLP4gq8zOqXjiBJRy7EoLpTuiWEN1qJfp9IJpRX2xjR1foEH6IBRatQgc0qrVPSBGunrFHWmN1cKzejfQmF5LSirRapSlqAvi4hX/TptEADJfBYZR31RZ1fLfl0kDz9+C1tghdTy5lWX2T+kLhZIRnjTQ3OSlrHwaFh3EX7jLad8HUKbqCGA069Ldb9OuhnRwtYgIZYhulIWdSHGDLSTUumEUnQrqaIOPTR4ygo5gCUWgjWZU1Cno8Fm6YWlGjybsFAGVmaSC4n2YepF+K/mPIQFUGKEBVjUYe9JtLDaoBja4hD6F/ElqRugi2uNYV+i7mOMJuDyQa9MUIfDzXggBpkWdTjgQ88Doz5csRuHcRLFiei5i5zYPsLIQLCb1JAas/uKOhjDxmIMW2IHC8ew+sfA6teJDpvvUmf6dVhuZY9mE5b6rKHOT6UN4HO1hzOjWR1dSUdVOWPYZKBbWGmPNThOsQEuheWlSFuCJQWoSsxlkfG1pE4bBBlFZ/Cy8yeH83UvUBd+jJkTOSjAyxcjXzA3gv1kTZ24/lCrgFeKjQo0jnDDhzIjVkckmzwcH+IMXCU62amZ7oCaOTJf51DnC878hiV99jH6VZc6NND06xB9DBU21M5owh9Y0XFaWxMwer6umzqRFUa/wnJwXYXoIqo8+rKoeNHCKoNCbL0jdtFu3SnPJhzqPsoscWOo+tVpuQ4UMyml9iZhWwV2E538zSOJ8KC8t1lnhXUdHFErQqMEhwI2Dq6NbtZE4Rlq0MPuEsJugz6eONR9qCdib+gJ1lF1dEa2umxf+lR0SzmMOE2cOeIjU9Q/lbSoG/zcaxmqrsD6Y9oanbzSxz2F+hdYIfQrUUdC1JGQEHUkRB0JUUdCQtSREHUkJP89dYMvJCSXlAFQN/ZISM4rs6Etc6KOhKgjIepISIg6kl+SusWSv65isX48sbDFenH45fFTsi7Xx2IeV9NVK2S5XHpvM+mYEcPnE6zbPjw8d4XnkXsctW/o6Ap3+BvKvBnHcXQF6pa9RVemqY3AtNfr9RcvJlGygpTT9tdiip+vytFEj1B677CcE0//NSM2kxOU7CeTjmTjMghyh9ksaSUJ4stTd2DECxJnQXBq2nR8bup6K7ve1utlv89fSGL46EPtLt2vhaz2H6du2mXi9ETqXjNiP9mepOfhkDqe5VkR/9zUAUsnU/emczlC3Rq9V3+57C37vT5XR+u+OHpc96br9UIF9tDXLXur/hrjPD6Fr6VJIgOn01W/twI+ODgm94uveb+pcPETGJ72UbGpQDhsjqQycE99SASm9aEgdeQt+j2Bu0Odk0Eo668aZB0j5pl77SYb6cmekb8JHvHNZPLgbfFoInDDSEUdxk2eJ/vhZO9NxgGzW1ie5lmQePM8y2M8yjKIhZoa58ybFRmWmyRx3pSvksgMSQKvKciSeT5nWcG1ljHkyz2dXXnYfIhNu07ZuhOkEUqkFp1SahmrI1W6om4I+eBVG/NZPuf52FIG+WXceB4U8/lMadEpZVzBsGHPZ69TJ5xcb7WEKun3lupoLWBZIWY9EwgJeG8N4SIl1ON0OW2SyEBgAcL7fYgFCp0vjZqmALqIQAVEWO0mHK4a6qQyLgJXj6KgtTqCL4B+3aLOyeAqaxsRB4Xrwri3m0z2m8ke2NtswPOJoy0S+Ay4bSYP2wdD3Way2U8eJg/Pkw2feNDAWggXQR4HyTjI0jyIvCQo4W8lgLpxlo15HqQsGHssCJLGE8kkKkMeQFuXZxF8sGCutWQZ/DWcp7OrblswQ7+jUraok0ZoPqUWlVJpmcNRAnYrAyV1nAUsDtIshcszDmaOMhWXBCiR0qJTyrgYTPXy3HsDdX1vARxp6sBvYY0Jf2Kom6566PFEygV8NS2syu4J8lbov9BZOl/Gwa1A+CMkFsGgZ9lTgWtR5FQdKWVrbUcfC1RHYOBa+EhJXVcGV1nLCKgKcA/8YQPt6oMHwO0RqB36vD06uslmB5ht4b84ehC+0LSwW/R6k4cN0AmuEBEOUuOCsLKzJAU6hgEbKrgDFmQziGNQ2ylQF3lMg6qSqAx5NguKKIgCBkelCgTq4qFnsreoEynNECIG4coI03gKLSql0jLHrmiQagMldWPUDjgVBYAVuMpUnGphlRaVUsVBXwMiordQ9+hxBEBRt/Y6qJN+TaZc6/6cSiICJRnoc/rCkdlfusL7KEgINJLQ3IH7BOpk4FIVq5P0VXfSok4dLYUphrquDK6ylhF6BCH8GhA00eMJ8HDia4PN7QSpQxQfdpNni7pnGfiArazIlTfeLkZnlCVFAFTn2ViFo3tAUFASj9nuQCVRGfKEB3OkbubxoFCB0B8ArrnO3qJOpDROLgfhygjjq4QWlVJpmcsk2kBJncgH/gxa5wB8lqNMxSnqlBaVUsfFwbzI+EnUPUI9rZCspYRvhdQ9KuqmTZL+dLlerxYqJTe+bqrAXGKr1leDXehF9d0vt8LBaWEXTFNnxiue1cvTyhZYujpUR+vesmM04WRwlXUa8bDzttiY7pEiAGqoqQNntpXDC0Wd6+uwPd5j+zrxRBfQy5sGGysgguqALx7k6KgEdVA9Y+MHGLOnbGQSlQGpG0vqoiBWgaIXF0SuG0FsYkld1O7gKyNMEy60qJRKi6JOGyipSyGJaCmDWWb8p1Km4iR5+lRUSh3Hs6zx+i+OJnpTqBHAbYkVo44MddCDXy5NkrW3xpoUKbHbJGbLRBIT2FcDxkc5eLW++BqbxbXdCeMt6h4Foi3q1jpQtbDiiGNfcrpEmmyVdgZXWduIuVVRHKgSgG12D5Pn/WQIfo4DT8Pd815TBx0+vt2KZngIKZE48IMTCH8e5zjvEBnXxdIA+3XQuYYyGPS9o7nu12XZfJ5ELnUqicpgqIuglz9TgTM2H0OfSWc3TTkUFKuUrbkcaYQ5ElpUSqVFUacNxGYYb4sikcZ7hblA5oxEHHbcxtFcaVEpTRxSeAp12PasZJPVX+sjQx0Gr1SgmK+beiqlSGqS6EBZ753zdQvRKPatuRokyaEOS5i2qROl409xqI8esclcyjlErzuDq6xlhD2a0FN1z9jgipEs+rkdfhvquGhv5VBWNM3QtmI4jHhn0L5aI2IYW0B3HK8/9MC9IfR74PbHMWyQ8TEOFdrUqSQyQ54q6iDh2FOBWEIA402VXd0r2KyL0USQH8ygKSOMt0ItOqXUMhbUpbp0oA5m7DgcxHkxA4CiZoSCyqCBl3F4w2J/QmpRKU3czB2kvTBft+AdX8YPPC6aQI7PJqC95TqO6yRN4EvPJk54gMBfDdRHi8XLWh4XC762JhNdI/i4UbrXU3V8y3H+ZKiidjvnSmwtM3aunYXd9M3UxZoND0qCyFmHvTKJziA7bWNHi1biZBcFmZSt2nYChZYmpWuEY+DsUNlsNuOi+6fj+HjWbYscuJzlOWznfPKRSeZrCs709Pr8zfn2kw9gfBTMrpiyPX/MitazlyNSsiA509P/xyk/MfC6slhO+6sfuBe2m+vbfjNO+fVStsGJYCfak4AtyjmnNSc/r9xcNSWtdCLxaH0dCQlRR/JzUjdMUPTMDv5+lUbTZZxh75HTtSV5K3WzIGNm2hJ2Tc0PFjC0FZVmfMyyoXVEQnI6dVHracrL1A0Ds3ILZqWtIxKSN1PHy1mbus6FgxyewUnOhllhHZGQ/AB16mGGRd2xhYOKs0ROdhN1JO+l7qaLOmfhoOZML80i6kh+kDomVullR3yds3BQccZz9WyOqCP5QerGsIA1nc9d6o4tHBScmeUFRB3Jf9mv61w4yMfjjI3HMzl0VUd0eUn+K+o6Fw4ORVucw1SddUSXl+SN1MUzs0BvOJs583XHFw6OZ3RJSd5BHYpKhEulD2aJf3Q5IAnJ0af/YxSdCH+3HduPLgckITlK3c1r6/1u6OKR0EonEqKOhISoI/l5qRvOXxu4/geDjHfuEXmuLSbd87pZnHyeb0j6qwh3/jR45/4N8Rup4+ovwr0Xdmk8+IvbH5C37BHpXWiLydY61af7+/unI0mf2pAdTfmryvb29nZvjm5Rfpw6BlPH8/HLuzQO/5MWOg0ulP3ULSZb61Sf7l9YpH+/+OTU8dstgGf8G/zY3e7eRh1/erI3YfP0Jo9qW0e9S6PaszEXa07GzgaQrV0o37lH5HW2mHTXqXIF1tPT4h5+8qd7EYAOcMEX90+wrYUT+PTZXB06uL0TAvtP7k3Yq9Txe3Prqq1V1CaPaltHvUuj3JdxFjPcFk3Fde5C+c49Iq+3xWRD3c3inut2FhiDrwU6vyfA7X4BHCJ8TuAno25/C//2VqO6A+fn3e53u+2J1Fk3aiy7bGqTR7Wto9mlUe1hFkvqRFznLpTv3CPy4ltMynzu2q2RoQ6wWvD7Jw7eTzhA/d8zgU+fr4Xd76FFtajbCR9327SyL1MHN6p1xeZyHYra5FFvdSYrzuzZqKgTcZ27UL5zj8iLbzEp87nUNS2s9HwoT8IBaurcwE/n6wCzhjouoLvZwZhiexp19/dWH9DZP7KhLm32c3Sp69yF8p17RF5vi0l7nariSHZ5ufR8i3v83/g6Fcg9/vn6ddCNa/p15hf4v90J1EGnjjvApONorDZ5NNSJXRqtfRlhlKviOnehfPcekVfZYrK1TvUJxxBcUQe9EM4X2MIuRH8EQiFOBz4t7j/fGHYvx7A7+bGFCTvO99wMZV/p17nzm9zeP1Jv6yh3aTT7Msa4mlPHde5C+c49Iq+zxWR7naocmSrquB4/iLGFtxBHrcBPOV+3xc+dmK/b8tvbU8ewNwf7R85a+0eawFnn9FXXLpTv2iPymltMHn/gwLnz9EId8pNfk/arPptwn0nsrv4c9p2LQq+yxSSt9PJ+7qf/N+97XnudLSZJfvY1JzdXzU5CK51+fbm9PXwETtQRdUQdUUfU0RvYPXoDu/e+N7ATdfQG9su/gZ2oozewX/4N7EQdvYH98m9gJ+o8j97Afuk3sBN1Hr2B/eJvYCfqPHoD+8XfwE7UefQG9ou/gZ2oozewX/4N7EQdvYH9Mm9gz6w3sBN19Ab2y7+BnaijN7Bf9A3sNyhEHb2B/WJvYL/Roqi7oWWBtObkzHJzc0gdcUfUXYi5ltC1IeouDh1hR9RdATrCjqg7J3PD583mf0Y2m+chcUfUnRm6Zws5Bd4zYUfUnZE6b3fAnOBuR9QRdWdzdd3QEXZE3Rmh845AB9h5nx47ou5M1D07oE1sBp+JOqLuLNANbcwmf3770x7LDj87dkTd+V0dMDf59m1Czo6oO/OsSePaNsLR4WcT9NlnT4i6M1OnvdzGamWJOqLurNRZrAF/G6KOqDsjddrVfUNRzH0zjSxRR9Sd42FYM2cCA1hJ3Z8TM6D47I/FiLrzULdxhrAo3zYHowmijqg7F3WbP6X8j6gj6s683OQl6p6JOqLuLCvrNsdb2M2QqCPqzuzsDqijNXZE3bmWrm+OtbAbWshO1J2LutfW1xF1RN0Z/kzn5bXERB1Rd56/Dnvp7yZozQlRd6a/STz+N2JEHVF3wT+FJeiIunO8lY+oI+o+rLPziDqi7tLYeUQdUXd56qiFJeoujZ1Hvo6ou/SYwiPqiLoL+zuPdhIj6i7r7mQcUUfUXY47jxwdUXd27jzHy5GjI+ouR57+R0LUkRB1JEQdCQlRR0LUkZAQdSREHQkJUUdC1JGQEHUkRB0JUUdCQtSREHUkJEQdCVFHQkLUkRB1JEQdUUdC1JEQdSQkRB0JUUdCQtSREHUkJEQdCVFHQtQRdSREHclnoe7/DBN0CR/Iyg8AAAAASUVORK5CYII=\" width=\"629\" height=\"169\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"paso-9--verificación-de-salud\">Paso 9 — Verificación de salud<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/#paso-9--verificaci%C3%B3n-de-salud\" class=\"hash-link\" aria-label=\"Enlace directo al Paso 9 — Verificación de salud\" title=\"Enlace directo al Paso 9 — Verificación de salud\" translate=\"no\">​</a></h2>\n<p>Salud del contenedor:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose </span><span class=\"token function\" style=\"color:#d73a49\">ps</span><br></div></code></pre></div></div>\n<p>Quieres <code>Up ... (healthy)</code>. Luego accede al endpoint de salud:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">curl</span><span class=\"token plain\"> http://127.0.0.1:18789/healthz</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># {\"ok\":true,\"status\":\"live\"}</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"docker compose ps saludable y la respuesta JSON de healthz\" src=\"https://wec.wiline.com/docs/es/assets/images/step9-health-2a25a28bc6b53dd20c385259373d72a5.png\" width=\"626\" height=\"217\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<p>Confirma que tu clave de proveedor es utilizable:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose run </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rm</span><span class=\"token plain\"> openclaw-cli models status</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># openai ... api_key=1 ... status=usable</span><br></div></code></pre></div></div>\n<div class=\"theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 16 16\"><path fill-rule=\"evenodd\" d=\"M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z\"></path></svg></span>Redactar antes de capturar pantalla</div><div class=\"admonitionContent_BuS1\"><p><code>models status</code> imprime un prefijo de clave enmascarado (<code>sk-proj-…</code>). Recorta o difumina antes de\npublicar la captura de pantalla.</p></div></div>\n<p>Ahora la verdadera prueba de extremo a extremo. El comando <code>agent</code> necesita un objetivo, así que lista los agentes\nprimero, luego envía un mensaje al predeterminado (<code>main</code>):</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose run </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rm</span><span class=\"token plain\"> openclaw-cli agents list</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># - main (predeterminado)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\" style=\"display:inline-block\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose run </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rm</span><span class=\"token plain\"> openclaw-cli agent </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--agent</span><span class=\"token plain\"> main </span><span class=\"token punctuation\" style=\"color:#393A34\">\\</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">  </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--message</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"Responde con exactamente una palabra: funcionando\"</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># funcionando</span><br></div></code></pre></div></div>\n<p>Si responde <code>funcionando</code>, toda la cadena — gateway → OpenAI → respuesta — está activa.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El agente respondiendo &amp;quot;funcionando&amp;quot; — la prueba\" src=\"https://wec.wiline.com/docs/es/assets/images/step9-agent-working-91514c6b42235714c1348d5ce9f6e9b9.png\" width=\"628\" height=\"391\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<p>La Control UI está en <code>http://&lt;tu-ip-instancia&gt;:18789/</code>; pega tu\n<code>OPENCLAW_GATEWAY_TOKEN</code> en Configuración para iniciar sesión.</p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Abrirlo desde otra máquina</div><div class=\"admonitionContent_BuS1\"><p>Los navegadores bloquean la Control UI a través de HTTP simple en una IP remota (\"Se requiere contexto de navegador seguro\"). Accede a ella a través de un túnel SSH para que se cargue como <code>127.0.0.1</code> — consulta\n<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/#3-control-ui-secure-browser-context-required\" class=\"\">Solución de problemas #3</a>.</p></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Interfaz de Control de OpenClaw en el navegador, iniciada sesión\" src=\"https://wec.wiline.com/docs/es/assets/images/step9-control-ui-fe1df8b0971ad1a8192ff40d2ea6856e.png\" width=\"1100\" height=\"567\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"solución-de-problemas-errores-reales\">Solución de problemas (errores reales)<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/#soluci%C3%B3n-de-problemas-errores-reales\" class=\"hash-link\" aria-label=\"Enlace directo al Solución de problemas (errores reales)\" title=\"Enlace directo al Solución de problemas (errores reales)\" translate=\"no\">​</a></h2>\n<p>Estos son errores que realmente encontramos durante este despliegue — no hipotéticos.</p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"1-eacces-permiso-denegado-mkdir-homenodeopenclawstate\">1. <code>EACCES: permiso denegado, mkdir '/home/node/.openclaw/state'</code><a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/#1-eacces-permiso-denegado-mkdir-homenodeopenclawstate\" class=\"hash-link\" aria-label=\"Enlace directo al 1-eacces-permiso-denegado-mkdir-homenodeopenclawstate\" title=\"Enlace directo al 1-eacces-permiso-denegado-mkdir-homenodeopenclawstate\" translate=\"no\">​</a></h3>\n<p>Ocurrió en la primera ejecución de incorporación cuando <code>~/.openclaw</code> no existía previamente:</p>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">[openclaw] El comando CLI falló.</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">[openclaw] Razón: Falló al abrir la base de datos de estado del plugin.</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">| EACCES: permiso denegado, mkdir '/home/node/.openclaw/state' | EACCES</span><br></div></code></pre></div></div>\n<p><strong>Causa:</strong> Docker creó automáticamente la fuente de montaje vinculada <code>~/.openclaw</code> propiedad de\n<code>root</code> (<code>0:0</code>). El contenedor se ejecuta como usuario <code>node</code> (uid 1000), por lo que no puede escribir\ndentro de ese directorio de propiedad de root.</p>\n<p><strong>Solución:</strong> da el directorio al uid 1000 (que también es tu usuario <code>ubuntu</code>):</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">sudo</span><span class=\"token plain\"> </span><span class=\"token function\" style=\"color:#d73a49\">chown</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-R</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">1000</span><span class=\"token plain\">:1000 ~/.openclaw</span><br></div></code></pre></div></div>\n<p>Precrear el directorio tú mismo (Paso 6) evita esto por completo.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"Error de permisos EACCES en la terminal\" src=\"https://wec.wiline.com/docs/es/assets/images/error1-eacces-fbbf355224203ecfccd68552fcf0bcb3.png\" width=\"968\" height=\"246\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"2-no-se-seleccionó-sesión-de-destino\">2. <code>No se seleccionó sesión de destino</code><a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/#2-no-se-seleccion%C3%B3-sesi%C3%B3n-de-destino\" class=\"hash-link\" aria-label=\"Enlace directo al 2-no-se-seleccionó-sesión-de-destino\" title=\"Enlace directo al 2-no-se-seleccionó-sesión-de-destino\" translate=\"no\">​</a></h3>\n<p>La primera vez que ejecutamos <code>agent</code> sin especificar a quién hablar:</p>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Error: No se seleccionó sesión de destino. Usa --agent &lt;id&gt;, --session-key &lt;key&gt;,</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">--session-id &lt;id&gt;, o --to &lt;E.164&gt;. Ejecuta openclaw agents list para ver agentes.</span><br></div></code></pre></div></div>\n<p><strong>Causa:</strong> <code>openclaw agent</code> ejecuta un turno contra un agente/sesión específica; sin\nun canal configurado no hay un objetivo implícito.</p>\n<p><strong>Solución:</strong> lista los agentes y pasa el id explícitamente:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose run </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rm</span><span class=\"token plain\"> openclaw-cli agents list          </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># muestra: main (predeterminado)</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose run </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rm</span><span class=\"token plain\"> openclaw-cli agent </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--agent</span><span class=\"token plain\"> main </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--message</span><span class=\"token plain\"> </span><span class=\"token string\" style=\"color:#e3116c\">\"...\"</span><br></div></code></pre></div></div>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El error &amp;quot;No se seleccionó sesión de destino&amp;quot;\" src=\"https://wec.wiline.com/docs/es/assets/images/error2-no-session-c405ca89ca87bd411080165c514046e5.png\" width=\"839\" height=\"241\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<h3 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"3-control-ui-se-requiere-contexto-de-navegador-seguro\">3. Control UI: \"Se requiere contexto de navegador seguro\"<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/#3-control-ui-se-requiere-contexto-de-navegador-seguro\" class=\"hash-link\" aria-label=\"Enlace directo al 3. Control UI: &quot;Se requiere contexto de navegador seguro&quot;\" title=\"Enlace directo al 3. Control UI: &quot;Se requiere contexto de navegador seguro&quot;\" translate=\"no\">​</a></h3>\n<p>Abrir la Control UI desde otra máquina (por ejemplo, macOS) en la IP de la VM a través de\nHTTP simple, la página se niega a conectar:</p>\n<div class=\"language-text codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-text codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Se requiere contexto de navegador seguro</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">Esta página se está ejecutando a través de HTTP simple, por lo que el navegador no puede crear la</span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\">identidad del dispositivo que espera el Gateway.</span><br></div></code></pre></div></div>\n<p><strong>Causa:</strong> los navegadores solo exponen las API criptográficas que OpenClaw necesita en un <em>contexto seguro</em> — HTTPS, o <code>localhost</code>/<code>127.0.0.1</code>. Un <code>http://&lt;ip&gt;:18789</code> remoto no es ninguno de los dos, por lo que está bloqueado en el <strong>navegador</strong>, independientemente de la bandera del lado del servidor\n<code>gateway.controlUi.allowInsecureAuth</code>.</p>\n<p><strong>Solución:</strong> tunela el puerto a través de SSH para que el navegador hable con <code>127.0.0.1</code> (un contexto seguro). En tu máquina local:</p>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">ssh</span><span class=\"token plain\"> </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-L</span><span class=\"token plain\"> </span><span class=\"token number\" style=\"color:#36acaa\">18789</span><span class=\"token plain\">:127.0.0.1:18789 ubuntu@</span><span class=\"token operator\" style=\"color:#393A34\">&lt;</span><span class=\"token plain\">tu-ip-vm</span><span class=\"token operator\" style=\"color:#393A34\">&gt;</span><br></div></code></pre></div></div>\n<p>Luego abre <code>http://127.0.0.1:18789/#token=&lt;tu-token&gt;</code> localmente. La solución adecuada a largo plazo es HTTPS a través de un proxy inverso — la próxima guía.</p>\n<p><span class=\"zoomImage__wrap\"><img alt=\"El mensaje rojo &amp;quot;Se requiere contexto de navegador seguro&amp;quot; en la Control UI\" src=\"https://wec.wiline.com/docs/es/assets/images/error3-secure-context-3e5792a56fae6a2ea2a8f9e0f0aa4c3f.png\" width=\"658\" height=\"895\" class=\"zoomImage \" loading=\"lazy\"><span class=\"zoomImage__badge\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"16\" height=\"16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"11\" cy=\"11\" r=\"7\"></circle><path d=\"M21 21l-4.3-4.3\"></path><path d=\"M11 8v6M8 11h6\"></path></svg></span></span></p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"notas-de-seguridad\">Notas de seguridad<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/#notas-de-seguridad\" class=\"hash-link\" aria-label=\"Enlace directo al Notas de seguridad\" title=\"Enlace directo al Notas de seguridad\" translate=\"no\">​</a></h2>\n<p>Al iniciar, el gateway registró advertencias que vale la pena actuar antes de cualquier exposición pública:</p>\n<ul>\n<li class=\"\"><strong>Vinculando a una dirección no de bucle invertido</strong> — el gateway escucha en la LAN. No\nexpongas el puerto 18789 a Internet público sin autenticación delante de él.</li>\n<li class=\"\"><strong><code>gateway.controlUi.allowInsecureAuth=true</code></strong> — marcado como peligroso. Ejecuta\n<code>docker compose run --rm openclaw-cli security audit</code>.</li>\n<li class=\"\"><strong><code>plugins.allow</code> está vacío</strong> — los plugins no empaquetados (por ejemplo, <code>codex</code>) pueden\ncargarse automáticamente. Establece una lista de permitidos explícita de ids de plugins de confianza.</li>\n</ul>\n<p>Endurecer esto detrás de un proxy inverso con HTTPS es el tema de la próxima guía.</p>\n<div class=\"theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success\"><div class=\"admonitionHeading_Gvgb\"><span class=\"admonitionIcon_Rf37\"><svg viewBox=\"0 0 12 16\"><path fill-rule=\"evenodd\" d=\"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z\"></path></svg></span>Habilidad desbloqueada 🏅</div><div class=\"admonitionContent_BuS1\"><p>Ahora puedes <strong>desplegar y operar tu propio agente de IA</strong> en una máquina que controlas.</p></div></div>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"lo-que-has-logrado\">Lo que has logrado<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/#lo-que-has-logrado\" class=\"hash-link\" aria-label=\"Enlace directo al Lo que has logrado\" title=\"Enlace directo al Lo que has logrado\" translate=\"no\">​</a></h2>\n<p>Pasaste de una instancia WEC vacía a un agente de IA autoalojado en funcionamiento:</p>\n<ul>\n<li class=\"\">Provisionaste y verificaste una VM de Ubuntu en WEC</li>\n<li class=\"\">Levantaste el gateway de OpenClaw con Docker Compose y la imagen preconstruida oficial</li>\n<li class=\"\">Conectaste tu propio proveedor de modelo (OpenAI) y confirmaste que funciona de extremo a extremo — el agente\nrespondió a través de la CLI y la Control UI</li>\n<li class=\"\">Encontraste (y solucionaste) los problemas reales de permisos, sesión y contexto seguro en el camino</li>\n</ul>\n<p>El agente es tuyo: tu clave, tu máquina, tus datos. A partir de aquí se trata de hacerlo\nseguro, accesible y útil.</p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"qué-puedes-hacer-con-él\">¿Qué puedes hacer con él?<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/#qu%C3%A9-puedes-hacer-con-%C3%A9l\" class=\"hash-link\" aria-label=\"Enlace directo al ¿Qué puedes hacer con él?\" title=\"Enlace directo al ¿Qué puedes hacer con él?\" translate=\"no\">​</a></h2>\n<p>OpenClaw no es un chatbot — es un <strong>agente autónomo que puede actuar en tu máquina</strong>. Ahora\nque está en funcionamiento, aquí hay lo que desbloquea (algunas funciones necesitan configuración adicional, y\ncubrimos las más importantes más adelante en esta serie):</p>\n<ul>\n<li class=\"\"><strong>Ejecutar trabajo real en la máquina.</strong> Puede ejecutar comandos de shell y leer/escribir archivos\n— <em>\"ver qué está usando el disco,\"</em> <em>\"seguir los registros y resumir los errores\"</em> — con\nsandboxing configurable sobre cuánto le permites tocar.</li>\n<li class=\"\"><strong>Hablar con él desde tu teléfono.</strong> OpenClaw se conecta a <strong>WhatsApp, Telegram, Discord,\nSlack, Signal e iMessage</strong>, por lo que delegas tareas conversacionalmente sin SSH.\n<em>(Conectamos Telegram en la próxima guía.)</em></li>\n<li class=\"\"><strong>Navegar y actuar en la web.</strong> La automatización de navegador incorporada le permite navegar por sitios,\nllenar formularios y extraer datos de forma autónoma.</li>\n<li class=\"\"><strong>Escribir y ejecutar código.</strong> Se integra con <strong>Claude Code</strong> para codificación autónoma —\nescribiendo y modificando código, ejecutando pruebas e incluso abriendo PRs.</li>\n<li class=\"\"><strong>Trabajar en segundo plano, proactivamente.</strong> Se ejecuta 24/7, puede ejecutar tareas en segundo plano,\ny realiza chequeos de \"latido\" en lugar de solo reaccionar cuando le envías un mensaje.</li>\n<li class=\"\"><strong>Crecer contigo.</strong> Una biblioteca de habilidades comunitarias (<strong>ClawHub</strong>), herramientas autoescritas y\norquestación de múltiples agentes significan que se vuelve más capaz con el tiempo — respaldado por una memoria persistente que lleva contexto a través de conversaciones.</li>\n</ul>\n<p>Usos reales típicos que la gente realiza: gestión de bandejas de entrada y calendarios, procesamiento de documentos,\ntareas de infraestructura en la máquina, tuberías de contenido y pruebas de código autónomas.</p>\n<hr>\n<div class=\"skillComplete\"><span class=\"skillComplete__medal\" aria-hidden=\"true\">🎯</span><div class=\"skillComplete__text\"><div class=\"skillComplete__title\">Finished this tutorial?</div><div class=\"skillComplete__sub\">Mark it complete to earn <strong>Deploy your own AI assistant</strong> on your skill path.</div></div><button type=\"button\" class=\"skillComplete__btn\">Mark as complete ✓</button></div>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"qué-sigue\">¿Qué sigue?<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/#qu%C3%A9-sigue\" class=\"hash-link\" aria-label=\"Enlace directo al ¿Qué sigue?\" title=\"Enlace directo al ¿Qué sigue?\" translate=\"no\">​</a></h2>\n<p>Esta es <strong>Parte 1</strong> de la serie <strong>Autoalojamiento de OpenClaw</strong> sobre cómo ejecutar infraestructura de IA de calidad de producción en una instancia WEC. Próximamente:</p>\n<ul>\n<li class=\"\"><strong>Parte 2 — <a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/secure-openclaw-caddy-https/\">Asegurar OpenClaw con un proxy inverso Caddy + HTTPS</a></strong> <em>(siguiente)</em> — pon HTTPS delante del gateway para que puedas eliminar la solución alternativa <code>allowInsecureAuth</code> y acceder a la Control UI desde cualquier lugar, de forma segura.</li>\n<li class=\"\"><strong>Parte 3 — <a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/add-telegram-channel-openclaw/\">Agregar un canal de Telegram</a></strong> — habla con tu agente desde tu teléfono.</li>\n<li class=\"\"><strong>Parte 4 — <a class=\"\" href=\"https://wec.wiline.com/docs/es/tutorials/netbird-mesh-vpn-openclaw/\">Hacer OpenClaw privado con una VPN de malla NetBird</a></strong> — colócalo en una malla privada y cierra los puertos públicos.</li>\n</ul>\n<p>Los archivos complementarios para esta guía — <code>docker-compose.yml</code> y <code>.env.example</code> — se encuentran en\nel repositorio de manifiestos de WiLine <em>(enlace que vendrá con el repositorio)</em>.</p>\n<hr>\n<h2 class=\"anchor anchorTargetStickyNavbar_Vzrq\" id=\"desmontaje\">Desmontaje<a href=\"https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/#desmontaje\" class=\"hash-link\" aria-label=\"Enlace directo al Desmontaje\" title=\"Enlace directo al Desmontaje\" translate=\"no\">​</a></h2>\n<div class=\"language-bash codeBlockContainer_Ckt0 theme-code-block\" style=\"--prism-color:#393A34;--prism-background-color:#f6f8fa\"><div class=\"codeBlockContent_QJqH\"><pre tabindex=\"0\" class=\"prism-code language-bash codeBlock_bY9V thin-scrollbar\" style=\"color:#393A34;background-color:#f6f8fa\"><code class=\"codeBlockLines_e6Vv\"><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose down              </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># detener + eliminar contenedores</span><span class=\"token plain\"></span><br></div><div class=\"token-line\" style=\"color:#393A34\"><span class=\"token plain\"></span><span class=\"token function\" style=\"color:#d73a49\">docker</span><span class=\"token plain\"> compose down </span><span class=\"token parameter variable\" style=\"color:#36acaa\">--rmi</span><span class=\"token plain\"> all </span><span class=\"token parameter variable\" style=\"color:#36acaa\">-v</span><span class=\"token plain\"> </span><span class=\"token comment\" style=\"color:#999988;font-style:italic\"># también eliminar la imagen y volúmenes</span><br></div></code></pre></div></div>\n<p>Para eliminar la configuración/estado en el host:\n<code>rm -rf ~/.openclaw ~/.openclaw-auth-profile-secrets</code>. Para detener la facturación por completo,\nelimina la VM desde el portal de WiLine.</p>",
            "url": "https://wec.wiline.com/docs/es/tutorials/deploy-openclaw-docker-compose/",
            "title": "Desplegar OpenClaw en una instancia WEC a través de Docker Compose",
            "summary": "Desde una instancia WEC nueva hasta un agente OpenClaw autoalojado que realmente responde — Docker Compose, tu propia clave de modelo y cada error real y solución de un despliegue en vivo.",
            "date_modified": "2026-06-18T00:00:00.000Z",
            "author": {
                "name": "Rafael Fernandes",
                "url": "https://www.linkedin.com/in/rafaelmacariofernandes/"
            },
            "tags": [
                "ai",
                "autoalojamiento",
                "docker",
                "openclaw",
                "vps"
            ]
        }
    ]
}