The AI-Native IDP -- Parte 8
La Arquitectura de Referencia
El Problema
Has leído siete artículos. Has visto el código para enriquecimiento del catálogo, scaffolding con IA, code review con contexto, RAG de documentación, governance e incident response. Cada artículo mostraba una pieza. Pero, ¿cómo encajan todas?
¿Dónde corre el servicio .NET de IA? ¿Cómo se conecta a PostgreSQL? ¿Cómo habla Backstage con él? ¿Cómo es el deployment en Kubernetes? ¿Qué variables de entorno necesitas? ¿Cómo añades una nueva funcionalidad de IA sin romper las que ya existen?
Este artículo responde a todo eso. Sin funcionalidades nuevas — solo la imagen completa.
La Solución
La arquitectura tiene cuatro capas:
- Backstage — Los plugins frontend y backend. Los desarrolladores interactúan con esto.
- AI Service — La Minimal API en .NET que gestiona todas las operaciones de IA. Los plugins de Backstage llaman a esto.
- PostgreSQL + pgvector — Almacena datos del catálogo, vector embeddings, logs de uso y policies.
- AI Provider — Cualquier API compatible con OpenAI: Scaleway Generative APIs con Mistral, Azure AI Foundry, o Mistral AI directamente.
graph TB
subgraph "Developer"
Browser[Browser]
end
subgraph "Backstage :7009"
FE[Frontend - React :3456]
BE[Backend - Node.js :7009]
P1[Catalog Enricher Plugin]
P2[AI Scaffolder Plugin]
P3[AI Code Review Plugin]
P4[TechDocs RAG Plugin]
P5[AI Governance Plugin]
P6[AI Incident Plugin]
end
subgraph "AI Service"
API[".NET Minimal API :5100"]
E1["/api/enrich"]
E2["/api/scaffold"]
E3["/api/review"]
E4["/api/index-doc"]
E5["/api/ask"]
E6["/api/incident/analyze"]
E7["/api/governance/*"]
E8["/api/scaffold-terraform"]
end
subgraph "Data"
PG[(PostgreSQL + pgvector)]
AO[AI Provider - Scaleway/Mistral/Azure]
end
subgraph "External"
GH[GitHub API]
WH[Webhooks - GitHub, Alertmanager]
end
Browser --> FE
FE --> BE
BE --> P1 & P2 & P3 & P4 & P5 & P6
P1 & P2 & P3 & P4 & P5 & P6 --> API
API --> E1 & E2 & E3 & E4 & E5 & E6 & E7 & E8
API --> PG
API --> AO
P1 & P3 & P6 --> GH
WH --> P3 & P6
Ejecución
La Estructura del Repositorio
forge/
├── app-config.yaml # Backstage configuration
├── catalog/
│ └── all.yaml # Catalog entities
├── templates/
│ ├── dotnet-api/ # Static template (article 1)
│ ├── ai-service/ # AI-powered template (article 3)
│ └── terraform-module/ # Terraform module template (devops-02)
├── modules/ # Real Terraform modules in the catalog
├── ai-service/
│ └── CatalogEnricher/
│ ├── Program.cs # All AI endpoints in one service
│ ├── CatalogEnricher.csproj
│ ├── AiUsageLogger.cs # Governance middleware
│ ├── appsettings.json
│ └── Dockerfile
├── plugins/
│ ├── catalog-enricher-backend/ # Article 2
│ ├── ai-scaffolder/ # Article 3
│ ├── ai-code-review/ # Article 4
│ ├── techdocs-rag/ # Article 5
│ ├── techdocs-rag-widget/ # Article 5 (frontend widget)
│ ├── ai-governance/ # Article 6
│ ├── ai-incident/ # Article 7
│ ├── admin/ # Admin plugin (frontend)
│ └── admin-backend/ # Admin plugin (backend)
├── packages/
│ ├── app/ # Backstage frontend
│ └── backend/
│ └── src/
│ └── index.ts # All plugin registrations
├── k8s/
│ ├── backstage.yaml # Backstage deployment
│ ├── ai-service.yaml # AI service deployment
│ └── postgresql.yaml # PostgreSQL with pgvector
├── .env.example
└── README.md
El Servicio .NET de IA Completo
Todos los endpoints viven en un solo Program.cs. Aquí tienes el archivo completo que combina todos los artículos. Cada endpoint sigue el mismo patrón: leer config, crear un ChatClient, construir un system prompt con contexto, llamar al modelo, devolver JSON estructurado.
La implementación completa de cada endpoint está en su propio artículo. Aquí mostramos las firmas reales y la infraestructura compartida — este es el archivo real, no un resumen.
// ai-service/CatalogEnricher/Program.cs
using System.ClientModel;
using System.Text.Json;
using System.Text.Json.Serialization;
using Azure.AI.OpenAI;
using Npgsql;
using OpenAI;
using OpenAI.Chat;
using OpenAI.Embeddings;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy("DevCors", policy =>
policy.WithOrigins("http://localhost:3456", "http://localhost:7007", "http://localhost:7008")
.AllowAnyHeader()
.AllowAnyMethod());
});
var app = builder.Build();
app.UseCors("DevCors");
app.MapGet("/healthz", () => Results.Ok(new { status = "healthy" }));
// --- Article 2: Catalog enrichment ---
app.MapPost("/api/enrich", async (EnrichRequest request, IConfiguration config) =>
{
// Validates request.Files is not empty
// Creates ChatClient from config (AI:Provider, AI:Endpoint, AI:Key, AI:ChatModel)
// System prompt: "Analyze source files, return JSON with description, tags, dependencies, apiEndpoints"
// Returns: CatalogMetadata { Description, Tags, Dependencies, ApiEndpoints }
// Full implementation → article 2
});
// --- Article 3: Project scaffolding ---
app.MapPost("/api/scaffold", async (ScaffoldRequest request, IConfiguration config) =>
{
// Validates request.Description is not empty
// Creates ChatClient from config
// System prompt: "Given a service description, produce name, type, dependencies, auth, kubernetes, nugetPackages, envVars, gotchaPrompt"
// Returns: ScaffoldResult with normalized gotchaPrompt (handles string or object)
// Full implementation → article 3
});
// --- Article 4: Code review ---
app.MapPost("/api/review", async (ReviewRequest request, IConfiguration config) =>
{
// Validates request.Diff is not empty
// Creates ChatClient from config
// System prompt includes: ServiceDescription, Tags, Dependencies, GotchaHeuristics
// Focus: architectural rule violations, security issues, service-specific patterns
// Returns: { review: string }
// Full implementation → article 4
});
// --- Article 5: RAG indexing ---
app.MapPost("/api/index-doc", async (IndexDocRequest request, IConfiguration config) =>
{
// Validates request.Content is not empty
// Creates EmbeddingClient from config (AI:EmbeddingModel, uses Rag:PostgresConnection)
// Splits content into 2000-char chunks, embeds each, upserts to doc_chunks table
// Returns: { chunksIndexed: int }
// Full implementation → article 5
});
// --- Article 5: RAG search ---
app.MapPost("/api/ask", async (AskRequest request, IConfiguration config) =>
{
// Validates request.Question is not empty
// Creates EmbeddingClient + ChatClient from config
// Embeds question → vector search in doc_chunks → top 5 results
// System prompt: "Answer using ONLY retrieved documentation, cite sources"
// Returns: AskResponse { Answer, Sources[] }
// Full implementation → article 5
});
// --- Article 6: Governance ---
app.MapGet("/api/governance/usage", async (string? action, string? team, int? days, IConfiguration config) =>
{
// Queries ai_usage_log grouped by action, team, status
// Returns: list of { Action, Team, Status, CallCount, TotalInputTokens, TotalOutputTokens, AvgDurationMs }
});
app.MapGet("/api/governance/costs", async (int? days, IConfiguration config) =>
{
// Queries ai_usage_log aggregated by day, calculates estimated cost
// Cost formula: (inputTokens * 2.0 / 1M) + (outputTokens * 6.0 / 1M)
// Returns: list of { Day, InputTokens, OutputTokens, EstimatedCostUsd }
});
app.MapGet("/api/governance/policies", async (IConfiguration config) =>
{
// Reads all rows from ai_policies table
// Returns: list of { Id, Team, Action, Enabled, MaxDailyCalls }
});
app.MapPut("/api/governance/policies", async (PolicyUpdate update, IConfiguration config) =>
{
// Upserts policy: INSERT ... ON CONFLICT (team, action) DO UPDATE
// Returns: Ok()
});
// --- Article 7: Incident response ---
app.MapPost("/api/incident/analyze", async (IncidentRequest request, IConfiguration config) =>
{
// Creates ChatClient from config
// System prompt includes: ServiceName, ServiceDescription, Dependencies, Tags,
// RecentDeployments, RecentErrors, GotchaHeuristics
// Returns: { analysis: string } with probable cause, evidence, suggested actions, related services
// Full implementation → article 7
});
// --- Infrastructure Hub: Terraform scaffolding (devops-02) ---
app.MapPost("/api/scaffold-terraform", async (ScaffoldTerraformRequest request, IConfiguration config) =>
{
// Validates request.Cloud against supported providers (azure, scaleway, aws, gcp)
// Creates ChatClient from config
// System prompt: "Generate Terraform module with main.tf, variables.tf, outputs.tf"
// Returns: ScaffoldTerraformResult { Main, Variables, Outputs }
// Full implementation → devops-02
});
app.Run();
// --- Record types ---
// Article 2
record EnrichRequest(List<SourceFile> Files);
record SourceFile(string Path, string Content);
record CatalogMetadata(
[property: JsonPropertyName("description")] string Description,
[property: JsonPropertyName("tags")] List<string> Tags,
[property: JsonPropertyName("dependencies")] List<string> Dependencies,
[property: JsonPropertyName("apiEndpoints")] List<string> ApiEndpoints);
// Article 3
record ScaffoldRequest(string Description);
record ScaffoldResult(
[property: JsonPropertyName("name")] string Name,
[property: JsonPropertyName("description")] string Description,
[property: JsonPropertyName("type")] string Type,
[property: JsonPropertyName("dependencies")] Dictionary<string, bool> Dependencies,
[property: JsonPropertyName("auth")] string Auth,
[property: JsonPropertyName("kubernetes")] bool Kubernetes,
[property: JsonPropertyName("nugetPackages")] List<string> NugetPackages,
[property: JsonPropertyName("envVars")] List<string> EnvVars,
[property: JsonPropertyName("gotchaPrompt")] JsonElement GotchaPrompt);
// Article 4
record ReviewRequest(
string ServiceName, string ServiceDescription,
string[] Tags, string[] Dependencies,
string GotchaHeuristics, string PrTitle, string Diff);
// Article 5
record IndexDocRequest(string EntityRef, string DocPath, string Content);
record AskRequest(string Question, string? EntityRef);
record AskResponse(string Answer, SourceReference[] Sources);
record SourceReference(string EntityRef, string DocPath, float Similarity);
// Article 6
record PolicyUpdate(string Team, string Action, bool Enabled, int? MaxDailyCalls);
// Article 7
record IncidentRequest(
string ServiceName, string ServiceDescription,
string[] Dependencies, string[] Tags,
string RecentDeployments, string RecentErrors,
string GotchaHeuristics, string AlertTitle,
string Severity, string StartedAt);
// Infrastructure Hub (devops-02)
record ScaffoldTerraformRequest(string Cloud, string Name, string Description);
record ScaffoldTerraformResult(string Main, string Variables, string Outputs);
static class SerializerOptions
{
public static readonly JsonSerializerOptions Default = new()
{
PropertyNameCaseInsensitive = true,
};
}
Cada endpoint que llama al modelo de IA usa la misma lógica de creación de ChatClient. El middleware de governance (AiUsageLogger.Track() del artículo 6) envuelve cada llamada de IA para registrar tokens y aplicar policies automáticamente.
Sobre seguridad: Estos endpoints no tienen autenticación, ni validación de input, ni rate limiting. Es a propósito — esta serie se centra en funcionalidad, no en hardening. Si piensas desplegarlo, lee la sección de Seguridad más abajo.
El Schema de Base de Datos Completo
-- pgvector (article 5)
CREATE EXTENSION IF NOT EXISTS vector;
-- Doc chunks for RAG (article 5)
CREATE TABLE doc_chunks (
id SERIAL PRIMARY KEY,
entity_ref VARCHAR(255) NOT NULL,
doc_path VARCHAR(500) NOT NULL,
chunk_index INTEGER NOT NULL,
content TEXT NOT NULL,
embedding vector(3584) NOT NULL, -- dimensions depend on the embedding model
created_at TIMESTAMP DEFAULT NOW(),
UNIQUE (entity_ref, doc_path, chunk_index)
);
CREATE INDEX ON doc_chunks
USING hnsw (embedding vector_cosine_ops);
-- AI usage log (article 6)
CREATE TABLE ai_usage_log (
id SERIAL PRIMARY KEY,
timestamp TIMESTAMP DEFAULT NOW(),
action VARCHAR(50) NOT NULL,
entity_ref VARCHAR(255),
team VARCHAR(100),
user_ref VARCHAR(255),
input_tokens INTEGER DEFAULT 0,
output_tokens INTEGER DEFAULT 0,
model VARCHAR(100),
duration_ms INTEGER DEFAULT 0,
status VARCHAR(20) DEFAULT 'success',
metadata JSONB DEFAULT '{}'
);
CREATE INDEX idx_usage_action ON ai_usage_log(action);
CREATE INDEX idx_usage_team ON ai_usage_log(team);
CREATE INDEX idx_usage_timestamp ON ai_usage_log(timestamp);
-- AI policies (article 6)
CREATE TABLE ai_policies (
id SERIAL PRIMARY KEY,
team VARCHAR(100),
action VARCHAR(50) NOT NULL,
enabled BOOLEAN DEFAULT true,
max_daily_calls INTEGER,
updated_at TIMESTAMP DEFAULT NOW(),
UNIQUE (team, action)
);
La Configuración de Backstage
El app-config.yaml conecta Backstage con el AI service y configura los plugins. Estas son las secciones clave que hacen que todo funcione:
# app-config.yaml (relevant sections)
app:
baseUrl: http://localhost:3456
backend:
baseUrl: http://localhost:7009
listen:
port: 7009
# Proxy — all Backstage plugins call the AI service through this
proxy:
endpoints:
/ai-service:
target: http://localhost:5100
# Plugin-specific config
catalogEnricher:
aiServiceUrl: http://localhost:5100
forge:
aiServiceUrl: http://localhost:5100
techdocs:
builder: 'local'
generator:
runIn: 'local'
publisher:
type: 'local'
La configuración del proxy es importante — los plugins de Backstage no llaman al AI service directamente. Pasan por proxy/ai-service, que el backend reenvía a http://localhost:5100. Esto mantiene la URL del AI service en un solo sitio.
Deployment en Kubernetes
El deployment del AI service:
# k8s/ai-service.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: forge-ai-service
labels:
app: forge-ai-service
spec:
replicas: 2
selector:
matchLabels:
app: forge-ai-service
template:
metadata:
labels:
app: forge-ai-service
spec:
containers:
- name: ai-service
image: ghcr.io/victorZKov/forge-ai-service:latest
ports:
- containerPort: 5100
env:
- name: AI__Provider
value: openai # "openai" for Scaleway/Mistral/OpenAI, "azure" for Azure AI Foundry
- name: AI__Endpoint
valueFrom:
secretKeyRef:
name: forge-secrets
key: AI_ENDPOINT
- name: AI__Key
valueFrom:
secretKeyRef:
name: forge-secrets
key: AI_KEY
- name: AI__ChatModel
value: mistral-small-3.2-24b-instruct-2506
- name: AI__EmbeddingModel
value: bge-multilingual-gemma2
- name: ConnectionStrings__Default
valueFrom:
secretKeyRef:
name: forge-secrets
key: POSTGRESQL_CONNECTION
livenessProbe:
httpGet:
path: /healthz
port: 5100
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /healthz
port: 5100
initialDelaySeconds: 5
periodSeconds: 10
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
---
apiVersion: v1
kind: Service
metadata:
name: forge-ai-service
spec:
selector:
app: forge-ai-service
ports:
- port: 5100
targetPort: 5100
Deployment de Backstage:
# k8s/backstage.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: forge-backstage
labels:
app: forge-backstage
spec:
replicas: 1
selector:
matchLabels:
app: forge-backstage
template:
metadata:
labels:
app: forge-backstage
spec:
containers:
- name: backstage
image: ghcr.io/victorZKov/forge-backstage:latest
ports:
- containerPort: 7009
env:
- name: POSTGRES_HOST
value: forge-postgresql
- name: POSTGRES_PORT
value: "5432"
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: forge-secrets
key: POSTGRES_USER
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: forge-secrets
key: POSTGRES_PASSWORD
- name: GITHUB_TOKEN
valueFrom:
secretKeyRef:
name: forge-secrets
key: GITHUB_TOKEN
livenessProbe:
httpGet:
path: /healthcheck
port: 7009
initialDelaySeconds: 30
periodSeconds: 30
readinessProbe:
httpGet:
path: /healthcheck
port: 7009
initialDelaySeconds: 15
periodSeconds: 10
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
---
apiVersion: v1
kind: Service
metadata:
name: forge-backstage
spec:
selector:
app: forge-backstage
ports:
- port: 7009
targetPort: 7009
Variables de Entorno
El .env.example completo:
# OIDC Authentication (QuantumID, Entra ID, Keycloak, Auth0)
OIDC_METADATA_URL=https://auth.quantumapi.eu/.well-known/openid-configuration
OIDC_CLIENT_ID=your-client-id
OIDC_CLIENT_SECRET=your-client-secret
BACKEND_SECRET=change-this-in-production
# AI Provider
# AI_PROVIDER: "openai" for Scaleway/Mistral/OpenAI (default), "azure" for Azure AI Foundry
AI_PROVIDER=openai
AI_ENDPOINT=https://api.scaleway.ai/v1
AI_KEY=your-key
AI_CHAT_MODEL=mistral-small-3.2-24b-instruct-2506
AI_EMBEDDING_MODEL=bge-multilingual-gemma2
# Examples for other providers:
# Mistral AI: AI_ENDPOINT=https://api.mistral.ai/v1 AI_CHAT_MODEL=mistral-large-latest
# OpenAI: AI_ENDPOINT=https://api.openai.com/v1 AI_CHAT_MODEL=gpt-5
# Azure: AI_PROVIDER=azure AI_ENDPOINT=https://your-instance.openai.azure.com AI_CHAT_MODEL=gpt-5
# PostgreSQL (shared between Backstage and AI service)
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_USER=forge
POSTGRES_PASSWORD=your-password
# GitHub
GITHUB_TOKEN=ghp_your-token
# AI Service
AI_SERVICE_URL=http://localhost:5100
# Webhook secrets
AI_CODE_REVIEW_WEBHOOK_SECRET=your-webhook-secret
Flexibilidad de Proveedor
El AI service usa dos paquetes NuGet: OpenAI y Azure.AI.OpenAI. El AzureOpenAIClient extiende OpenAIClient, así que todo el código de los endpoints funciona con ambos — la única diferencia es cómo creas el client. Pon AI:Provider a "azure" para Azure AI Foundry, o déjalo como "openai" (por defecto) para todo lo demás.
| Provider | AI:Provider | Endpoint | Chat model | Embedding model |
|---|---|---|---|---|
| Scaleway | openai | https://api.scaleway.ai/v1 | mistral-small-3.2-24b-instruct-2506 | bge-multilingual-gemma2 |
| Mistral AI | openai | https://api.mistral.ai/v1 | mistral-large-latest | mistral-embed |
| Azure AI Foundry | azure | https://your-instance.openai.azure.com | gpt-5, claude-sonnet-4-6, mistral-large | text-embedding-3-small |
| OpenAI | openai | https://api.openai.com/v1 | gpt-5 | text-embedding-3-small |
Azure AI Foundry es la opción más flexible — aloja modelos de OpenAI, Anthropic, Mistral y otros en un solo endpoint. Puedes ejecutar Claude Sonnet o Mistral Large a través de Azure sin gestionar API keys separadas por proveedor. El proyecto Forge funciona sobre Scaleway con Mistral Large, pero Azure AI Foundry es una buena elección si tu organización ya usa Azure.
El Registro del Backend de Backstage
Todos los plugins registrados en un solo sitio:
// packages/backend/src/index.ts
import { createBackend } from '@backstage/backend-defaults';
const backend = createBackend();
// Core plugins (standard Backstage)
backend.add(import('@backstage/plugin-app-backend'));
backend.add(import('@backstage/plugin-catalog-backend'));
backend.add(import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'));
backend.add(import('@backstage/plugin-catalog-backend-module-logs'));
backend.add(import('@backstage/plugin-scaffolder-backend'));
backend.add(import('@backstage/plugin-scaffolder-backend-module-github'));
backend.add(import('@backstage/plugin-techdocs-backend'));
backend.add(import('@backstage/plugin-auth-backend'));
backend.add(import('@backstage/plugin-auth-backend-module-guest-provider'));
backend.add(import('@backstage/plugin-proxy-backend'));
// Search (PostgreSQL-backed)
backend.add(import('@backstage/plugin-search-backend'));
backend.add(import('@backstage/plugin-search-backend-module-pg'));
backend.add(import('@backstage/plugin-search-backend-module-catalog'));
backend.add(import('@backstage/plugin-search-backend-module-techdocs'));
// Permissions
backend.add(import('@backstage/plugin-permission-backend'));
backend.add(import('@backstage/plugin-permission-backend-module-allow-all-policy'));
// Kubernetes
backend.add(import('@backstage/plugin-kubernetes-backend'));
// OIDC auth (QuantumID, Entra ID, Keycloak, Auth0)
backend.add(import('./modules/auth'));
// AI modules — extend existing plugins
import { catalogEnricherModule } from '@internal/plugin-catalog-enricher-backend';
backend.add(catalogEnricherModule); // Article 2 (catalog module)
import { aiScaffoldModule } from './modules/aiScaffoldModule';
backend.add(aiScaffoldModule); // Article 3 (scaffolder module)
// AI plugins — standalone with own routes
import { aiCodeReviewPlugin } from '@internal/plugin-ai-code-review';
backend.add(aiCodeReviewPlugin); // Article 4
import { techDocsRagPlugin } from '@internal/plugin-techdocs-rag';
backend.add(techDocsRagPlugin); // Article 5
import { aiIncidentPlugin } from '@internal/plugin-ai-incident';
backend.add(aiIncidentPlugin); // Article 7
// Admin plugin
import { adminBackendPlugin } from '@internal/plugin-admin-backend';
backend.add(adminBackendPlugin);
backend.start();
La distinción importa: el catalog enricher y el AI scaffolder son modules (createBackendModule) porque extienden plugins existentes (catalog y scaffolder). Los plugins de code review, RAG e incident son standalone plugins (createBackendPlugin) porque tienen sus propias rutas HTTP.
El governance dashboard (artículo 6) es un plugin solo de frontend — lee datos del AI service a través del proxy y no necesita un backend plugin. Se registra en App.tsx, no aquí.
Cómo Añadir una Nueva Funcionalidad de IA
Cada funcionalidad de IA en Forge sigue el mismo patrón:
- Añadir un endpoint al AI service — Un nuevo
app.MapPostenProgram.cs. Recibe contexto, llama al modelo de IA, devuelve un resultado estructurado. - Crear un backend plugin de Backstage — Llama al endpoint del AI service. Lee datos del catálogo como contexto. Se ejecuta en un schedule o responde a eventos.
- Crear un componente frontend de Backstage — Una card, página o widget que muestra resultados al desarrollador.
- Envolver con governance — Usar
AiUsageLogger.Track()alrededor de la llamada de IA. Los logs se generan automáticamente. Las policies se aplican solas.
Ejemplo: añadir un “dependency vulnerability scanner”:
// 1. AI service endpoint
app.MapPost("/api/scan-deps", async (ScanRequest request, IConfiguration config) =>
{
// Same pattern: create ChatClient from IConfiguration, system prompt with context
});
// 2. Backstage backend plugin
export const depScanPlugin = createBackendPlugin({
pluginId: 'dep-scanner',
register(env) {
// Same pattern: schedule, iterate catalog, call AI service
},
});
// 3. Frontend component
export const DepScanCard = () => {
// Same pattern: fetch from proxy, render results
};
La arquitectura es la misma cada vez. Lo que cambia es la IA. La fontanería no.
Costes Reales
Con el governance dashboard del artículo 6, así se ve el uso real para un equipo de 20 desarrolladores:
| Funcionalidad | Frecuencia | Tokens/llamada | Coste mensual |
|---|---|---|---|
| Catalog enrichment | Ciclo 24h, ~30 servicios | ~2K input, ~500 output | ~$0.21 |
| Scaffolding | ~10 servicios nuevos/mes | ~1K input, ~2K output | ~$0.14 |
| Code review | ~200 PRs/mes | ~5K input, ~1K output | ~$3.20 |
| RAG queries | ~500 preguntas/mes | ~3K input, ~800 output | ~$5.40 |
| Incident analysis | ~5 incidentes/mes | ~4K input, ~1K output | ~$0.07 |
Total: ~$9/mes para un equipo de 20 desarrolladores. Menos que la comida de un desarrollador.
Estimado con precios de Mistral Small en Scaleway Generative APIs. Las llamadas de embedding (RAG indexing) usan bge-multilingual-gemma2 — coste insignificante. Si cambias a Claude Sonnet o GPT-5 vía Azure AI Foundry, los costes son más altos pero la arquitectura es la misma.
El governance dashboard rastrea esto automáticamente. Si los costes crecen, lo ves. Si un equipo está haciendo demasiadas RAG queries, puedes poner un límite diario.
Seguridad: Lo Que Falta
Dejamos la seguridad fuera a propósito. Esta serie va de funcionalidad — catalog enrichment, scaffolding, code review, RAG, governance, incident response. Cada artículo se centra en hacer que la funcionalidad funcione, no en hardenizarla.
Pero no lleves esto a producción sin arreglar estas cosas:
- Sin autenticación en los endpoints de IA — Cualquiera con acceso a la red puede llamar a
/api/enrich,/api/scaffold, o cualquier otro endpoint. No hay validación JWT, ni API key check, nada. - Sin validación de input — Un usuario podría enviar un prompt manipulado en el campo
Descriptionque haga que la IA haga algo inesperado. La prompt injection es real. - Sin rate limiting — Un simple bucle llamando a
/api/askrepetidamente podría quemar tu presupuesto del AI provider en minutos. Las governance policies solo bloquean después de alcanzar el límite diario — no hacen throttle de requests. - Sin scrubbing de PII — Código fuente, logs de errores y datos de incidentes van directamente al AI provider. Si esos datos contienen información personal, salen de tu infraestructura.
La serie AI en Producción cubre todo esto:
- Governance — PII scrubbing, consent middleware, audit logging
- Control de Costes — Rate limiting por usuario, input token guards
- Diseñar para el Fallo — Circuit breakers, timeouts, fallbacks
- Preparado para Producción — Checklist completo antes de salir a producción
Mismo servicio .NET, misma arquitectura. La serie de producción añade las capas que necesitas antes de que usuarios reales lo toquen.
La Serie
| Artículo | Qué hace | Plugin |
|---|---|---|
| 1. Por Qué Tu IDP No Ayuda | Setup de Backstage + catálogo + template estático | — |
| 2. Enseñar a Tu Catálogo a Pensar | La IA lee código, actualiza metadata del catálogo | catalog-enricher-backend |
| 3. Software Templates con IA | Scaffolder que entiende lenguaje natural + GOTCHA.md | ai-scaffolder |
| 4. El Plugin de AI Code Review | PR review con contexto del catálogo + heurísticas GOTCHA | ai-code-review |
| 5. TechDocs RAG | Búsqueda vectorial sobre documentación de la plataforma | techdocs-rag |
| 6. El Dashboard de AI Governance | Tracking de uso, estimación de costes, control de policies | ai-governance |
| 7. Incident Response Asistido por IA | Diagnóstico automático de incidentes desde catálogo + logs | ai-incident |
| 8. La Arquitectura de Referencia | Este artículo — cómo encaja todo | — |
Troubleshooting
Problemas comunes al montar Forge:
- El AI service devuelve 502 — Endpoint o API key incorrectos. Comprueba
AI:EndpointyAI:Keyenappsettings.json. Prueba a llamar a/healthzdirectamente:curl http://localhost:5100/healthz. - Backstage no llega al AI service — Comprueba que la configuración del proxy en
app-config.yamlapunta ahttp://localhost:5100. El proxy endpoint debería ser/ai-service. - La extensión pgvector no se encuentra — Ejecuta
CREATE EXTENSION IF NOT EXISTS vector;en tu base de datos PostgreSQL antes de crear la tabladoc_chunks. - Errores de CORS en el navegador — El frontend corre en 3456, el backend en 7009, el AI service en 5100. Los tres tienen que estar en la configuración CORS del
Program.cs. - El catalog enricher no se ejecuta — Comprueba
catalogEnricher.aiServiceUrlenapp-config.yaml. El module se ejecuta en un schedule — mira los logs de Backstage para ver errores. - TechDocs RAG devuelve vacío — Indexa documentos primero con
/api/index-docantes de consultar con/api/ask. Sin índice = sin resultados. - La búsqueda no funciona — Necesitas
@backstage/plugin-search-backend-module-pgpara búsqueda con PostgreSQL. La búsqueda in-memory por defecto no persiste.
Qué Viene Después
Forge es un punto de partida. La arquitectura soporta cualquier funcionalidad de IA que siga el mismo patrón: contexto del catálogo, inteligencia del modelo, control del governance.
Tus servicios están catalogados y mejorados con IA. Ahora haz lo mismo con tu infraestructura. La siguiente serie — Infrastructure Hub — extiende Forge para gestionar módulos Terraform, recursos cloud y entornos multi-tenant. Mismo Backstage, mismo AI service, misma filosofía.
La idea: registrar módulos Terraform como entidades del catálogo, generar golden-path modules con IA (usando el mismo endpoint /api/scaffold-terraform que ya tienes), y gestionar infraestructura para equipos internos y clientes MSP desde una sola instancia de Backstage. Stay tuned.
El código está en GitHub: victorZKov/forge. Cada artículo tiene su tag correspondiente (article-01 hasta article-08).
Y si quieres estructurar tus prompts de IA para mejores resultados — no solo en el IDP, sino en cualquier desarrollo asistido por IA — echa un vistazo a la serie ATLAS + GOTCHA. De ahí viene el formato de prompt GOTCHA usado en toda esta serie.
Si esta serie te ayuda, puedes invitarme a un café.
Este es el artículo 8 — el artículo final de la serie AI-Native IDP. Anterior: Incident Response Asistido por IA.
Loading comments...