# Cómo crear y consumir un agente en Azure Foundry

En este tutorial aprenderás a:

Crear un agente en Azure Foundry usando un modelo de OpenAI. Configurar permisos y autenticación con Service Principal. Consumir el agente desde un script Node.js. Integrarlo en un frontend simple (Angular) para crear un chat funcional.

## **Crear el agente en Azure Foundry**

Se debe acceder al portal de **Azure Foundry**. Posteriormente se debe navegar al apartado de **Agents** y seleccionar **Create Agent**.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1762871421403/7f3b11f1-d783-4e4b-8a2c-474c6cc123b0.png align="center")

Una vez en este apartado, se debe elegir un **deployment de OpenAI** (Azure Foundry actualmente soporta modelos de OpenAI como GPT-4, GPT-3.5, etc.).

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1762871619031/624e4c86-f918-4720-8a9b-fbe93fd52701.png align="center")

En la pantalla se debe configurar lo siguiente:

* **Nombre del agente**.
    
* **Prompt inicial** (instrucciones del comportamiento).
    
* **Knowledge base** (sube tus PDFs o documentos relevantes).
    
    * Los documentos que se suben se vectorizan mediante Azure AI Search para la optimización de las búsquedas en la base de conocimientos.
        

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1762871808808/eeb86308-6927-47a0-b784-477303b1eb0f.png align="center")

Finalmente hay que guardar los cambios y esperar a que el agente esté listo.

## Configurar autenticación con Service Principal

Para consumir el agente desde tu aplicación, necesitas un Service Principal, por lo cual debes navegar hacia el apartado de Azure Active Directory → App Registrations → New Registration.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1762872153197/0a09b95a-992b-4f92-80a6-5f10d48025fb.png align="center")

Entonces se debe asignar un nombre ej. `foundry-agent-client`). Una vez creado se debe copiar el valor del **Application (client) ID** y el **Directory (tenant) ID**.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1762872437654/0c348440-a654-44f1-89c5-f226f71c587d.png align="center")

Posteriormente se debe generar un Client Secret, para esto se debe navegar a la sección de Certificates & Secrets → New Client Secret. Guarda el valor en un lugar seguro (lo usarás en tu script) ya que este valor no aparecerá nuevamente.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1762872579586/a2f26dd6-f5b5-4b48-9d52-08d8ac8f8ce0.png align="center")

## Asignar permisos al Service Principal

Ve a Azure Foundry → IAM (Access Control). En este apartado añade el Service Principal como Cognitive Services Contributor. Esto le permitirá interactuar con el agente vía API.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1762872724728/f2ff3770-c8cf-49a1-b5bb-e54d242bdc7e.png align="center")

## Consumir el agente desde Node.js

Crea un pequeño script para conectarte al agente, para esto crea un proyecto de Node.js (Solo para el ejemplo, se puede usar también Python o .NET).

Primero debes instalar las siguientes dependencias:

```bash
npm install @azure/ai-projects @azure/identity body-parser dotenv express
```

Posteriormente se debe crear el archivo **.env** con los siguientes valores:

```bash

AZURE_TENANT_ID=<TENAND_ID>
AZURE_CLIENT_SECRET=<CLIENT_SECRET>
AZURE_CLIENT_ID=<CLIENT_ID>
PROJECT_URL=
AGENT_ID=
```

Finalmente se debe ejecutar el código para consumir el proyecto, en el cual

```javascript
// Importar dependencias
const express = require("express");
const bodyParser = require("body-parser");

// Importar variables de entorno desde archivo .env
require('dotenv').config()

// Importaciones del SDK de Agentes de Azure AI
const { AIProjectClient } = require("@azure/ai-projects");
const { DefaultAzureCredential } = require("@azure/identity");

const app = express();
const PORT = 3000;

// *** Configuración específica de tu Agente de Azure AI Studio ***
const AZURE_AI_PROJECT_URL = process.env.PROJECT_URL;
const AGENT_ID = process.env.AGENT_ID;

// Inicializar el cliente del proyecto (se autentica usando az login en desarrollo)
const aiProjectClient = new AIProjectClient(
  AZURE_AI_PROJECT_URL,
  new DefaultAzureCredential() //Esta credencial por defecto busca el tenant, clientId y clientSecret
);

// Aplicar middleware de body parser
app.use(bodyParser.json());

// Configuración de CORS para permitir la comunicación con tu Frontend (Angular)
app.use((req, res, next) => {
  // CAMBIA ESTO por la URL de tu frontend (ej: 'http://localhost:4200')
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Content-Type");
  res.header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
  next();
});

// Endpoint principal para el chat
app.post("/api/chat", async (req, res) => {
  const userMessage = req.body.message;

  if (!userMessage) {
    return res.status(400).json({ error: "Falta el mensaje del usuario." });
  }

  try {
    // 1. Crear un nuevo Hilo (Thread) para la conversación
    const thread = await aiProjectClient.agents.threads.create();

    // 2. Enviar el mensaje del usuario al Hilo
    await aiProjectClient.agents.messages.create(
      thread.id,
      "user",
      userMessage
    );

    // 3. Iniciar la Ejecución (Run) del Agente sobre el Hilo
    let run = await aiProjectClient.agents.runs.create(thread.id, AGENT_ID);

    // 4. Monitorear el estado del Run hasta que termine
    while (run.status === "queued" || run.status === "in_progress") {
      // Esperar 1 segundo antes de volver a consultar el estado
      await new Promise((resolve) => setTimeout(resolve, 1000));
      run = await aiProjectClient.agents.runs.get(thread.id, run.id);
    }

    if (run.status === "failed") {
      console.error("Run failed:", run.lastError);
      return res.status(500).json({
        error: "La ejecución del Agente falló.",
        details: run.lastError,
      });
    }

    // 5. Recuperar todos los mensajes después de que el Agente ha respondido
    const messagesIterator = aiProjectClient.agents.messages.list(thread.id, {
      order: "desc",
    });

    const allMessages = [];
    for await (const m of messagesIterator) {
      allMessages.push(m);
    }

    // Ahora, puedes usar .find() en el array allMessages
    const agentResponseMsg = allMessages.find((m) => m.role === "assistant");

    let agentReplyText = "Lo siento, no pude obtener una respuesta válida.";

    if (agentResponseMsg) {
      // ... el resto de tu lógica para extraer el texto ...
      const content = agentResponseMsg.content.find(
        (c) => c.type === "text" && "text" in c
      );
      if (content) {
        agentReplyText = content.text.value;
      }
    }
    // 6. Retornar la respuesta al Frontend
    res.json({ reply: agentReplyText });
  } catch (error) {
    console.error("Error al comunicarse con Azure AI Agents:", error);
    res
      .status(500)
      .json({ error: "Error interno del servidor al procesar la solicitud." });
  }
});

app.listen(PORT, () => {
  console.log(`Servidor intermedio escuchando en http://localhost:${PORT}`);
});
```

## Crear un frontend simple (Angular)

En este caso la aplicación será en Angular, pero puede ser cualquier frontend como React, Vue.js, etc. Para el ejemplo, se debe crear primero un servicio que se comunique con el backend.

```javascript
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

// Define la estructura de la respuesta
export interface ChatResponse {
  reply: string;
}

@Injectable({
  providedIn: 'root'
})
export class ChatService {
  private apiUrl = 'http://localhost:3000/api/chat'; // URL de tu backend

  constructor(private http: HttpClient) { }

  sendMessage(message: string): Observable<ChatResponse> {
    // Envía el mensaje al backend intermediario
    return this.http.post<ChatResponse>(this.apiUrl, { message: message });
  }
}
```

Luego se debe construir una pantalla básica para visualizar el resultado del chat. En este ejemplo se usa Bootstrap y CSS.

Se utilizarán los siguientes estilos:

```css
/* Limita el alto de la ventana de chat y habilita el scroll */
.chat-window {
    height: 400px;
    overflow-y: auto;
    padding-bottom: 10px;
    background-color: #f8f9fa; /* Gris muy claro */
}

/* Estilo de la burbuja: limita el ancho y añade esquinas */
.message-bubble {
    max-width: 70%; /* Las burbujas no ocupan todo el ancho */
    border-radius: 1.25rem; /* Más redondeado que el card default */
    border: none;
    box-shadow: 0 1px 2px rgba(0,0,0,.1);
}

/* Mensajes del Agente: alineación a la izquierda (default) */
.bg-light.message-bubble {
    /* Ajustes para el lado del agente */
    border-top-left-radius: 0.25rem; /* Esquina superior izquierda menos redondeada para simular el chat */
}

/* Mensajes del Usuario: alineación a la derecha */
.bg-primary.message-bubble {
    /* Ajustes para el lado del usuario */
    border-top-right-radius: 0.25rem; /* Esquina superior derecha menos redondeada */
    margin-left: auto; /* Para asegurar el espaciado correcto */
}
```

Posteriormente, en el archivo Typescript se debe declarar los valores necesarios para consumir los datos.

```typescript
import { Component, OnInit } from '@angular/core';
import { ChatService } from 'src/app/services/chat.service';


interface Message {
  text: string;
  sender: 'user' | 'agent';
}
@Component({
  selector: 'app-chatbot-ai',
  templateUrl: './chatbot-ai.component.html',
  styleUrls: ['./chatbot-ai.component.scss']
})
export class ChatbotAiComponent implements OnInit {
    messages: Message[] = [];
    currentMessage: string = '';

    constructor(private chatService: ChatService) {}

    sendMessage(): void {
        if (this.currentMessage.trim() === '') return;

        const userMsg = this.currentMessage;
        this.messages.push({ text: userMsg, sender: 'user' });
        this.currentMessage = ''; // Limpiar el input

        // Llama al servicio que se comunica con tu backend intermedio
        this.chatService.sendMessage(userMsg).subscribe({
          next: (response) => {
            this.messages.push({ text: response.reply, sender: 'agent' });
          },
          error: (err) => {
            this.messages.push({ text: 'Error: No se pudo contactar al agente.', sender: 'agent' });
            console.error(err);
          }
        });
    }
}
```

Por último se puede mostrar en la interfaz un diseño básico para visualizar los datos de la interacción.

```xml
<div class="container mt-4">
    <div class="card shadow-sm">
        <div class="card-header bg-primary text-white">
            <h5 class="mb-0">Agente de Azure AI Foundry</h5>
        </div>
        
        <div class="card-body chat-window">
            
            <div *ngFor="let msg of messages" class="d-flex mb-3" 
                 [ngClass]="{'justify-content-end': msg.sender === 'user'}">
                
                <div class="card message-bubble" 
                     [ngClass]="{'bg-primary text-white': msg.sender === 'user', 'bg-light': msg.sender === 'agent'}">
                    <div class="card-body p-2">
                        {{ msg.text }}
                    </div>
                </div>
            </div>

        </div>

        <div class="card-footer">
            <div class="input-group">
                <input type="text" 
                       class="form-control" 
                       [(ngModel)]="currentMessage" 
                       (keyup.enter)="sendMessage()" 
                       placeholder="Escribe un mensaje...">
                <button class="btn btn-primary" 
                        (click)="sendMessage()"
                        [disabled]="!currentMessage.trim()">
                    Enviar
                </button>
            </div>
        </div>
    </div>
</div>
```

## **Probar la integración**

* Inicia tu backend Node.js.
    
* Abre tu frontend Angular.
    
* Envía mensajes y recibe respuestas del agente.
    

## **Recomendaciones**

* **Seguridad**: Usa HTTPS y JWT para proteger tu API.
    
* **Escalabilidad**: Considera usar Azure API Management para exponer tu API.
    
* **Monitoreo**: Activa Azure Monitor para logs y métricas.
    
* **Optimización**: Ajusta el prompt y la base de conocimiento para mejorar la calidad de las respuestas.
