@envisiongroup/create-alakazam 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +5 -0
- package/README.md +56 -0
- package/bin/create-alakazam.mjs +7 -0
- package/package.json +28 -0
- package/src/args.mjs +72 -0
- package/src/createAlakazamCli.mjs +115 -0
- package/src/projectName.mjs +33 -0
- package/src/prompts.mjs +63 -0
- package/src/registry.mjs +62 -0
- package/src/template.mjs +135 -0
- package/templates/default/.env.sample +55 -0
- package/templates/default/README.md +66 -0
- package/templates/default/gitignore +7 -0
- package/templates/default/package.json +36 -0
- package/templates/default/src/mastra/access/role-rules.ts +31 -0
- package/templates/default/src/mastra/agents/azure-claude-sandbox-agent/azure-claude-sandbox-agent.ts +108 -0
- package/templates/default/src/mastra/agents/azure-openai-sandbox-agent/azure-openai-sandbox-agent.ts +111 -0
- package/templates/default/src/mastra/agents/azure-openai-sandbox-agent/prompts/index.ts +42 -0
- package/templates/default/src/mastra/agents/user-context-agent/prompts/index.ts +93 -0
- package/templates/default/src/mastra/agents/user-context-agent/tools/get-my-profile-tool.ts +62 -0
- package/templates/default/src/mastra/agents/user-context-agent/user-context-agent.ts +87 -0
- package/templates/default/src/mastra/agents/weather-agent/weather-agent.ts +68 -0
- package/templates/default/src/mastra/index.ts +47 -0
- package/templates/default/src/mastra/scorers/weather-scorer.ts +92 -0
- package/templates/default/src/mastra/tools/weather-tool.ts +110 -0
- package/templates/default/src/mastra/workflows/weather-workflow.ts +198 -0
- package/templates/default/tsconfig.json +16 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# **PROJECT_NAME**
|
|
2
|
+
|
|
3
|
+
Agents hub [Mastra](https://mastra.ai) construido sobre
|
|
4
|
+
[`@envisiongroup/alakazam`](https://www.npmjs.com/package/@envisiongroup/alakazam).
|
|
5
|
+
|
|
6
|
+
Todo el wiring transversal (auth Entra ID, storage multi-dialecto, rutas de
|
|
7
|
+
chat/catálogo/adjuntos, observability, CORS) lo resuelve `createAgentsHub()`;
|
|
8
|
+
este repo solo declara su dominio en `src/mastra/`.
|
|
9
|
+
|
|
10
|
+
## Setup
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
cp .env.sample .env # rellena AZURE_MODEL_*, AZURE_API_* y OPENAI_API_KEY
|
|
14
|
+
pnpm install
|
|
15
|
+
pnpm dev # Mastra Studio en http://localhost:4111
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
> **npm privado**: `@envisiongroup/alakazam` es un paquete restringido del scope
|
|
19
|
+
> `@envisiongroup`. Necesitas un token npm con acceso al scope configurado en tu
|
|
20
|
+
> `~/.npmrc` (`//registry.npmjs.org/:_authToken=...`) antes de `pnpm install`.
|
|
21
|
+
|
|
22
|
+
## Qué incluye el template
|
|
23
|
+
|
|
24
|
+
| Recurso | Demuestra |
|
|
25
|
+
| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
26
|
+
| `agents/weather-agent` | Agente mínimo: `hubAgent()` (catálogo + acceso público), `getAzureClient()`, tool propia, scorers, `Memory`. |
|
|
27
|
+
| `agents/azure-openai-sandbox-agent` | Patrones avanzados: emisión de reasoning (`emitReasoning: true` en `hubAgent()` + `reasoningSummary`), adjuntos con `fileAttachmentProcessor` (uploads), generación de archivos con `code_interpreter` y links de descarga (downloads), `capabilities` vía `hubAgent()`. |
|
|
28
|
+
| `agents/azure-claude-sandbox-agent` | Provider alternativo: Claude vía Azure AI Foundry (capa de compatibilidad Responses API), adjuntos PDF/imágenes nativos, fail-closed de uploads Office (sin `code_interpreter`). |
|
|
29
|
+
| `agents/user-context-agent` | Contexto del usuario autenticado: `requestContextSchema`, instrucciones dinámicas con la identidad del usuario y tools condicionales según haya (o no) usuario en el `requestContext`. Sin Microsoft Graph (el profile resolver es Noop sin env vars → funciona out-of-the-box). |
|
|
30
|
+
| `tools/weather-tool` | `createTool` con schemas zod contra una API pública. |
|
|
31
|
+
| `workflows/weather-workflow` | `createWorkflow` + `createStep` con schemas zod y metadata `hubWorkflow()` (catálogo + acceso). |
|
|
32
|
+
| `scorers/weather-scorer` | Scorers prebuilt de `@mastra/evals` + un scorer LLM-judged custom. |
|
|
33
|
+
| `access/role-rules.ts` | Dónde mapear claims del IdP (grupos Entra, dominios, emails) → roles de aplicación. |
|
|
34
|
+
|
|
35
|
+
Si generaste el proyecto con `--minimal`, solo queda la suite weather (agente,
|
|
36
|
+
tool, workflow y scorer): los tres agents de ejemplo avanzados no se copian.
|
|
37
|
+
|
|
38
|
+
> **Adjuntos en Mastra Studio (limitación conocida)**: el composer del chat de
|
|
39
|
+
> Studio solo envía `image/*`, PDF, video y audio como _file parts_. Los
|
|
40
|
+
> archivos Office/CSV (DOCX, XLSX, PPTX, CSV) los lee como texto y llegan
|
|
41
|
+
> corruptos al modelo — es un bug de Mastra upstream (vivo en `main` y en
|
|
42
|
+
> `mastra@1.19.0`, sin fix publicado). Para probar el flujo de adjuntos
|
|
43
|
+
> Office de `azure-openai-sandbox-agent` end-to-end usa el SPA autenticado;
|
|
44
|
+
> en Studio puedes probar el pipeline de adjuntos con PDF.
|
|
45
|
+
|
|
46
|
+
## Convenciones clave
|
|
47
|
+
|
|
48
|
+
- **Acceso por recurso**: todo agent/workflow registrado declara su acceso en
|
|
49
|
+
`metadata.envisionHub` vía `hubAgent()` — sin eso el arranque falla
|
|
50
|
+
(default-deny, fail-fast).
|
|
51
|
+
- **Imports del paquete**: usa los subpaths del exports map, p. ej.
|
|
52
|
+
`@envisiongroup/alakazam/services/azure-model`. Los archivos internos no
|
|
53
|
+
son importables.
|
|
54
|
+
- **Peers**: `@mastra/*`, `@ai-sdk/*`, `ai` y `zod` son peerDependencies de
|
|
55
|
+
alakazam (una sola instancia de Mastra para no romper singletons). Los
|
|
56
|
+
dialectos de storage (`pg`, `mssql`) son peers opcionales: instálalos solo
|
|
57
|
+
si cambias el `DATABASE_URL` por defecto.
|
|
58
|
+
|
|
59
|
+
## Scripts
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pnpm dev # desarrollo con Mastra Studio
|
|
63
|
+
pnpm build # build de producción (mastra build)
|
|
64
|
+
pnpm start # servir el build
|
|
65
|
+
pnpm typecheck # tsc --noEmit
|
|
66
|
+
```
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__PROJECT_NAME__",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Agents hub Mastra construido sobre @envisiongroup/alakazam.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=22.13.0"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"dev": "mastra dev",
|
|
12
|
+
"build": "mastra build",
|
|
13
|
+
"start": "mastra start",
|
|
14
|
+
"typecheck": "tsc --noEmit"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@envisiongroup/alakazam": "__ALAKAZAM_VERSION__",
|
|
18
|
+
"@ai-sdk/azure": "^3.0.51",
|
|
19
|
+
"@ai-sdk/openai": "^3.0.49",
|
|
20
|
+
"@mastra/ai-sdk": "^1.6.2",
|
|
21
|
+
"@mastra/core": "^1.51.0",
|
|
22
|
+
"@mastra/editor": "^0.13.7",
|
|
23
|
+
"@mastra/evals": "^1.5.1",
|
|
24
|
+
"@mastra/libsql": "^1.16.0",
|
|
25
|
+
"@mastra/loggers": "^1.2.0",
|
|
26
|
+
"@mastra/memory": "^1.23.0",
|
|
27
|
+
"@mastra/observability": "^1.16.1",
|
|
28
|
+
"ai": "^6.0.149",
|
|
29
|
+
"zod": "^4.3.6"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "^25.5.0",
|
|
33
|
+
"mastra": "^1.19.0",
|
|
34
|
+
"typescript": "^5.9.3"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { RoleRule } from '@envisiongroup/alakazam';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Reglas del proveedor de identidad → roles de aplicación de este cliente.
|
|
5
|
+
*
|
|
6
|
+
* Este es el único lugar del cliente que conoce datos del proveedor de
|
|
7
|
+
* identidad, como grupos de Entra, dominios y correos. Los agentes y
|
|
8
|
+
* workflows declaran roles lógicos en `metadata.envisionHub.access`; aquí se
|
|
9
|
+
* define quién recibe esos roles según sus claims.
|
|
10
|
+
*
|
|
11
|
+
* Estado actual: todos los recursos invocables del hub son `{ public: true }`
|
|
12
|
+
* o `{ internal: true }`, así que no hacen falta reglas. Cuando un recurso
|
|
13
|
+
* declare `access: { roles: ['mi-rol'] }`, agregar la regla que lo otorga:
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* // Grupo de Entra ID (Object ID del grupo) → rol de aplicación:
|
|
17
|
+
* { when: { provider: 'entra', group: '8f14e45f-…' }, grant: 'cochilco-viewer' },
|
|
18
|
+
* // Dominio (tid Entra / hd Google Workspace) → rol:
|
|
19
|
+
* { when: { provider: 'google', domain: 'envisiongroup.com' }, grant: 'employee' },
|
|
20
|
+
* // Email directo → rol (útil para admins puntuales):
|
|
21
|
+
* { when: { email: 'admin@envisiongroup.com' }, grant: 'hub-admin' },
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* Requiere que la App Registration de la API tenga configurado
|
|
25
|
+
* `groupMembershipClaims` (p. ej. "SecurityGroup") para que el JWT incluya
|
|
26
|
+
* el claim `groups`. El Object ID del grupo se obtiene en
|
|
27
|
+
* Entra ID → Groups → propiedad "Object ID".
|
|
28
|
+
*/
|
|
29
|
+
export const ROLE_RULES: RoleRule[] = [
|
|
30
|
+
// { when: { provider: 'entra', group: '<OBJECT-ID-DEL-GRUPO>' }, grant: 'mi-rol' },
|
|
31
|
+
];
|
package/templates/default/src/mastra/agents/azure-claude-sandbox-agent/azure-claude-sandbox-agent.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { Agent } from '@mastra/core/agent';
|
|
2
|
+
import { Memory } from '@mastra/memory';
|
|
3
|
+
import { getAzureClient } from '@envisiongroup/alakazam/services/azure-model';
|
|
4
|
+
import {
|
|
5
|
+
fileAttachmentProcessor,
|
|
6
|
+
uploadGrantStore,
|
|
7
|
+
uploadProviderRegistry,
|
|
8
|
+
} from '@envisiongroup/alakazam/services/uploads';
|
|
9
|
+
import { attachments, hubAgent } from '@envisiongroup/alakazam';
|
|
10
|
+
|
|
11
|
+
// Crea el cliente de Azure y selecciona el modelo LLM que se usara.
|
|
12
|
+
const azureClient = getAzureClient();
|
|
13
|
+
|
|
14
|
+
// Claude se expone mediante Azure AI Foundry. Usamos `getAzureClient` en lugar
|
|
15
|
+
// de `@ai-sdk/anthropic` porque Microsoft ofrece una capa de compatibilidad
|
|
16
|
+
// sobre los deployments de Foundry. Esta capa recibe el formato de Azure OpenAI
|
|
17
|
+
// Responses API y lo convierte internamente al formato de Anthropic Messages.
|
|
18
|
+
// Consideraciones:
|
|
19
|
+
//
|
|
20
|
+
// + Reutilizamos el mismo SDK y las variables de entorno que gpt-*.
|
|
21
|
+
// + Los PDF llegan mediante Azure Files (`file_id`), no como base64 inline.
|
|
22
|
+
// Esto reduce la latencia y el consumo de tokens.
|
|
23
|
+
// + `provider-selector` resuelve `azure.responses` como `azure-files`, por lo
|
|
24
|
+
// que también se aplican las validaciones de seguridad y deduplicación.
|
|
25
|
+
// - Algunas funciones de Claude no están disponibles en esta capa: la visión
|
|
26
|
+
// puede ser inconsistente, y no se exponen `extended_thinking` ni el
|
|
27
|
+
// almacenamiento temporal de prompts con TTL personalizado.
|
|
28
|
+
// - Un cambio en el conversor de Microsoft podría afectar este agente.
|
|
29
|
+
//
|
|
30
|
+
// Si necesitas OCR de imágenes, visión avanzada, `extended_thinking`, cache de
|
|
31
|
+
// prompts, citas u otra función exclusiva de Claude, usa el SDK nativo
|
|
32
|
+
// `@ai-sdk/anthropic` con `getAnthropicClient`. Esa opción requiere configurar
|
|
33
|
+
// `AZURE_ANTHROPIC_BASE_URL`.
|
|
34
|
+
const llmModel = azureClient('claude-opus-4-7');
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Agente de prueba general para Claude. Pensado para experimentar con
|
|
38
|
+
* adjuntos (PDFs principalmente) y validar features del flujo del modulo de
|
|
39
|
+
* uploads contra el modelo. NO tiene proposito de negocio especifico — para
|
|
40
|
+
* ese tipo de uso, crear un agente dedicado.
|
|
41
|
+
*/
|
|
42
|
+
export const azureClaudeSandboxAgent = new Agent({
|
|
43
|
+
id: 'azure-claude-sandbox-agent',
|
|
44
|
+
name: 'Sandbox Azure Claude',
|
|
45
|
+
description:
|
|
46
|
+
'Agente sandbox para probar Claude (via Azure AI Foundry) con adjuntos. Acepta PDFs y los lee nativamente. Sin caso de uso especifico — para experimentar con prompts, adjuntos y comportamiento del modelo.',
|
|
47
|
+
instructions: `
|
|
48
|
+
Eres un asistente de proposito general construido sobre Claude.
|
|
49
|
+
|
|
50
|
+
# Como respondes
|
|
51
|
+
- En español, claro y conciso. Sin formalismos innecesarios.
|
|
52
|
+
- Si el usuario te hace una pregunta puntual, contesta directo. No agregues estructura tipo "1, 2, 3" si no aporta.
|
|
53
|
+
- Si el usuario te pide algo que requiere mas analisis (resumir un documento, comparar, extraer datos), si conviene estructurar la respuesta — usa secciones / listas / tablas markdown segun ayude.
|
|
54
|
+
|
|
55
|
+
# Adjuntos
|
|
56
|
+
- PDFs e imagenes: el usuario puede adjuntar estos archivos y vos los lees directamente como contenido visual. No tenes sandbox de Python.
|
|
57
|
+
- Para cualquier calculo numerico que hagas sobre tablas del PDF o imagen, mostra el desglose paso a paso para que el usuario pueda auditar. No alucines cifras: si una celda no se lee bien, decilo.
|
|
58
|
+
- Otros formatos (XLSX/DOCX/PPTX/CSV): si el usuario adjunta algo asi, indicale que hoy este agente solo soporta PDFs e imagenes. No intentes adivinar contenido.
|
|
59
|
+
|
|
60
|
+
# Limites
|
|
61
|
+
- No tenes acceso a internet ni a tools externas.
|
|
62
|
+
- No podes generar archivos binarios (Excel, Word, imagenes). Si el usuario los pide, ofrece el contenido como markdown / CSV / texto inline.
|
|
63
|
+
- Si una pregunta requiere informacion que no tenes en el contexto, decilo en vez de inventar.
|
|
64
|
+
`,
|
|
65
|
+
model: llmModel,
|
|
66
|
+
metadata: {
|
|
67
|
+
...hubAgent({
|
|
68
|
+
catalog: { tags: ['sandbox', 'claude', 'azure'] },
|
|
69
|
+
access: { public: true },
|
|
70
|
+
capabilities: {
|
|
71
|
+
attachments: [attachments.pdf, attachments.images],
|
|
72
|
+
},
|
|
73
|
+
}),
|
|
74
|
+
},
|
|
75
|
+
// No se declara `code_interpreter`: aunque Claude se accede via la capa de
|
|
76
|
+
// compatibilidad de Azure OpenAI Responses API, el tool `code_interpreter`
|
|
77
|
+
// NO esta soportado para deployments de Claude (es feature OpenAI-only).
|
|
78
|
+
// Sin esta tool declarada, el `fileAttachmentProcessor` entra en
|
|
79
|
+
// fail-closed para Office docs y no intenta subirlos.
|
|
80
|
+
inputProcessors: [
|
|
81
|
+
// Intercepta `FileUIPart` inline del usuario. Como `model.provider` es
|
|
82
|
+
// `azure.responses` (capa de compat), el `provider-selector` resuelve
|
|
83
|
+
// `azure-files`. Resultado por tipo de adjunto:
|
|
84
|
+
//
|
|
85
|
+
// - PDF: sube a Azure Files API, emite marker firmado con HMAC,
|
|
86
|
+
// `processLLMRequest` lo reinyecta como `input_file.file_id`. La
|
|
87
|
+
// capa de compat de Foundry traduce eso a Claude `document` block.
|
|
88
|
+
// - XLSX/DOCX/PPTX/CSV: delivery seria `code-interpreter-container`,
|
|
89
|
+
// pero como NO pasamos `codeInterpreterFactory` el processor entra
|
|
90
|
+
// en fail-closed → devuelve la FileUIPart original. El AI SDK la
|
|
91
|
+
// rechaza con `AI_UnsupportedFunctionalityError`, error claro.
|
|
92
|
+
// - Imagen: no esta en el whitelist actual (`SUPPORTED_UPLOAD_FORMATS`)
|
|
93
|
+
// → fluye inline. Resultado depende de si la capa de compat de
|
|
94
|
+
// Foundry traduce bien `input_image` a Claude `image` block (a
|
|
95
|
+
// veces si, a veces no — feature en evolucion).
|
|
96
|
+
fileAttachmentProcessor({
|
|
97
|
+
grantStore: uploadGrantStore,
|
|
98
|
+
providerRegistry: uploadProviderRegistry,
|
|
99
|
+
model: llmModel,
|
|
100
|
+
// SIN `codeInterpreterFactory`: Claude no soporta esa tool. El
|
|
101
|
+
// fail-closed del processor evita uploads inutiles a Azure para
|
|
102
|
+
// Office docs.
|
|
103
|
+
}),
|
|
104
|
+
],
|
|
105
|
+
// SIN outputProcessors: no hay `code_interpreter` generando archivos para
|
|
106
|
+
// descargar.
|
|
107
|
+
memory: new Memory(),
|
|
108
|
+
});
|
package/templates/default/src/mastra/agents/azure-openai-sandbox-agent/azure-openai-sandbox-agent.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Agent } from '@mastra/core/agent';
|
|
2
|
+
import { Memory } from '@mastra/memory';
|
|
3
|
+
import { getAzureClient } from '@envisiongroup/alakazam/services/azure-model';
|
|
4
|
+
import { attachments, hubAgent } from '@envisiongroup/alakazam';
|
|
5
|
+
import {
|
|
6
|
+
createAzureCodeInterpreterDownloadProcessor,
|
|
7
|
+
downloadGrantStore,
|
|
8
|
+
} from '@envisiongroup/alakazam/services/downloads';
|
|
9
|
+
import {
|
|
10
|
+
fileAttachmentProcessor,
|
|
11
|
+
uploadGrantStore,
|
|
12
|
+
uploadProviderRegistry,
|
|
13
|
+
} from '@envisiongroup/alakazam/services/uploads';
|
|
14
|
+
import { buildInstructions } from './prompts';
|
|
15
|
+
|
|
16
|
+
// Crea el cliente de Azure y selecciona el modelo LLM que se usara.
|
|
17
|
+
const azureClient = getAzureClient();
|
|
18
|
+
|
|
19
|
+
// Asegurate que el modelo especificado exista en tu proyecto de Azure Foundry
|
|
20
|
+
const llmModel = azureClient('gpt-5.4');
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Agente de prueba general sobre gpt-5.4 mediante Azure OpenAI Responses API.
|
|
24
|
+
* Incluye tres capacidades que puedes reutilizar en otros agentes:
|
|
25
|
+
*
|
|
26
|
+
* - `code_interpreter` para procesar adjuntos de Office y generar archivos.
|
|
27
|
+
* - `fileAttachmentProcessor` para montar XLSX, DOCX, PPTX y CSV en el
|
|
28
|
+
* contenedor del sandbox.
|
|
29
|
+
* - `createAzureCodeInterpreterDownloadProcessor` para crear enlaces de
|
|
30
|
+
* descarga para los archivos generados.
|
|
31
|
+
*
|
|
32
|
+
* Úsalo para experimentar con prompts, adjuntos y respuestas del modelo. No
|
|
33
|
+
* está asociado a un caso de negocio específico.
|
|
34
|
+
*/
|
|
35
|
+
export const azureOpenAISandboxAgent = new Agent({
|
|
36
|
+
id: 'azure-openai-sandbox-agent',
|
|
37
|
+
name: 'Sandbox Azure OpenAI',
|
|
38
|
+
description:
|
|
39
|
+
'Agente sandbox para probar gpt-5.4 (via Azure OpenAI Responses API) con adjuntos y generacion de archivos. Acepta PDF / XLSX / DOCX / PPTX / CSV y puede generar archivos como entregable. Sin caso de uso especifico — para experimentar.',
|
|
40
|
+
instructions: () => buildInstructions(),
|
|
41
|
+
model: llmModel,
|
|
42
|
+
// Permite que la ruta envíe al cliente los fragmentos `reasoning-*`.
|
|
43
|
+
// Sin esta opción, el provider los filtra antes de enviarlos al cliente.
|
|
44
|
+
//
|
|
45
|
+
// Para que el reasoning realmente aparezca en el cliente hacen falta
|
|
46
|
+
// DOS cosas en paralelo:
|
|
47
|
+
// 1. `emitReasoning: true` en `hubAgent()`
|
|
48
|
+
// 2. `providerOptions.openai.reasoningSummary: 'auto'` (abajo) — pide
|
|
49
|
+
// al provider que genere el summary del reasoning.
|
|
50
|
+
// Si falta cualquiera de las dos, el reasoning no llega al cliente.
|
|
51
|
+
metadata: {
|
|
52
|
+
...hubAgent({
|
|
53
|
+
catalog: { tags: ['sandbox', 'openai', 'azure'] },
|
|
54
|
+
access: { public: true },
|
|
55
|
+
capabilities: {
|
|
56
|
+
attachments: [
|
|
57
|
+
attachments.pdf,
|
|
58
|
+
attachments.images,
|
|
59
|
+
attachments.office,
|
|
60
|
+
],
|
|
61
|
+
generatedFiles: true,
|
|
62
|
+
},
|
|
63
|
+
emitReasoning: true,
|
|
64
|
+
}),
|
|
65
|
+
},
|
|
66
|
+
defaultOptions: {
|
|
67
|
+
providerOptions: {
|
|
68
|
+
openai: {
|
|
69
|
+
// `reasoningEffort`: cuánto "piensa" el modelo internamente.
|
|
70
|
+
// `low` mantiene latencia baja para el sandbox.
|
|
71
|
+
reasoningEffort: 'low', // 'minimal' | 'low' | 'medium' | 'high'
|
|
72
|
+
// `reasoningSummary: 'auto'`: pide al provider que emita
|
|
73
|
+
// un resumen del razonamiento como chunks `reasoning-*`
|
|
74
|
+
// streamed. Combinado con `emitReasoning: true` en
|
|
75
|
+
// `hubAgent()` (que emite `metadata.emitReasoning`)
|
|
76
|
+
// y `sendReasoning: true` en el route, el cliente useChat
|
|
77
|
+
// los renderiza como sección "Razonando…" colapsable.
|
|
78
|
+
reasoningSummary: 'auto',
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
// `code_interpreter` inicial sin `container.fileIds`. Cuando el usuario
|
|
83
|
+
// adjunta XLSX, DOCX, PPTX o CSV, `fileAttachmentProcessor` guarda los
|
|
84
|
+
// identificadores en `requestContext` y reemplaza esta tool por otra que
|
|
85
|
+
// incluye el contenedor con esos archivos.
|
|
86
|
+
tools: {
|
|
87
|
+
code_interpreter: azureClient.tools.codeInterpreter(),
|
|
88
|
+
},
|
|
89
|
+
inputProcessors: [
|
|
90
|
+
fileAttachmentProcessor({
|
|
91
|
+
grantStore: uploadGrantStore,
|
|
92
|
+
providerRegistry: uploadProviderRegistry,
|
|
93
|
+
model: llmModel,
|
|
94
|
+
// Habilita la rama Office/CSV: cuando hay file_ids en el
|
|
95
|
+
// requestContext, el processor usa esta factory en
|
|
96
|
+
// processInputStep para reemplazar el code_interpreter del step
|
|
97
|
+
// por uno con container cargado.
|
|
98
|
+
codeInterpreterFactory: (args) =>
|
|
99
|
+
azureClient.tools.codeInterpreter(args),
|
|
100
|
+
}),
|
|
101
|
+
],
|
|
102
|
+
outputProcessors: [
|
|
103
|
+
// Genera enlaces de descarga absolutos usando el origen de la petición.
|
|
104
|
+
// Funciona detrás de un proxy y tanto en Mastra Studio como en un SPA
|
|
105
|
+
// con un origen diferente.
|
|
106
|
+
createAzureCodeInterpreterDownloadProcessor({
|
|
107
|
+
grantStore: downloadGrantStore,
|
|
108
|
+
}),
|
|
109
|
+
],
|
|
110
|
+
memory: new Memory(),
|
|
111
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import {
|
|
2
|
+
codeInterpreterUsage,
|
|
3
|
+
composeInstructions,
|
|
4
|
+
immediateFeedback,
|
|
5
|
+
} from '@envisiongroup/alakazam/prompts/common/index.js';
|
|
6
|
+
|
|
7
|
+
const immediateFeedbackExamples = [
|
|
8
|
+
'Recibí el archivo. Voy a revisarlo primero y luego usaré code_interpreter para los cálculos necesarios.',
|
|
9
|
+
'Ya identifiqué los datos relevantes. Ahora voy a ejecutar el análisis en Python.',
|
|
10
|
+
'Voy a generar el archivo solicitado y dejarlo listo para descarga.',
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const roleAndGoal = `Eres un asistente de propósito general construido sobre gpt-5.4 corriendo en Azure OpenAI.`;
|
|
14
|
+
|
|
15
|
+
const responseStyle = `# Como respondes
|
|
16
|
+
|
|
17
|
+
- En español, claro y conciso. Sin formalismos innecesarios.
|
|
18
|
+
- Pregunta directa → respuesta directa. No agregues estructura tipo "1, 2, 3" si no aporta.
|
|
19
|
+
- Si te piden algo que requiere mas analisis (resumir documento, comparar, extraer datos, hacer un calculo grande), si conviene estructurar — usa secciones / listas / tablas markdown segun ayude.`;
|
|
20
|
+
|
|
21
|
+
const limits = `# Limites
|
|
22
|
+
|
|
23
|
+
- No tienes acceso a internet ni a tools externas mas allá de \`code_interpreter\`.
|
|
24
|
+
- Si una pregunta requiere informacion que no tienes en el contexto ni en los adjuntos, dilo en vez de inventar.`;
|
|
25
|
+
|
|
26
|
+
export function buildInstructions() {
|
|
27
|
+
return composeInstructions([
|
|
28
|
+
roleAndGoal,
|
|
29
|
+
immediateFeedback({
|
|
30
|
+
toolName: 'code_interpreter',
|
|
31
|
+
examples: immediateFeedbackExamples,
|
|
32
|
+
}),
|
|
33
|
+
responseStyle,
|
|
34
|
+
codeInterpreterUsage({
|
|
35
|
+
toolName: 'code_interpreter',
|
|
36
|
+
additionalRules: [
|
|
37
|
+
'Para una pregunta puntual con una cuenta corta (por ejemplo, 1 + 1), no es necesario abrir un sandbox.',
|
|
38
|
+
],
|
|
39
|
+
}),
|
|
40
|
+
limits,
|
|
41
|
+
]);
|
|
42
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AuthenticatedUserContextValue,
|
|
3
|
+
EnrichedUserProfile,
|
|
4
|
+
} from '@envisiongroup/alakazam';
|
|
5
|
+
|
|
6
|
+
const responseStyle = `# Como respondes
|
|
7
|
+
|
|
8
|
+
- En español, claro y conciso.
|
|
9
|
+
- Pregunta directa → respuesta directa. No agregues estructura tipo "1, 2, 3" si no aporta.`;
|
|
10
|
+
|
|
11
|
+
const limits = `# Limites
|
|
12
|
+
|
|
13
|
+
- No tienes acceso a internet ni a tools externas más allá de las declaradas.
|
|
14
|
+
- Si una pregunta requiere información que no tienes en el contexto, dilo en vez de inventar.`;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Instrucciones del `user-context-agent`: cambian según haya (o no) un
|
|
18
|
+
* usuario autenticado en el `requestContext` del request. Es la demo del
|
|
19
|
+
* patrón "instrucciones dinámicas por contexto".
|
|
20
|
+
*
|
|
21
|
+
* - SIN usuario: el agente explica qué demuestra y que el usuario debe
|
|
22
|
+
* autenticarse (el hub corre detrás de auth; en Mastra Studio local no
|
|
23
|
+
* hay IdP, así que este camino es el esperado ahí).
|
|
24
|
+
* - CON usuario: el agente sabe quién es (oid/upn/nombre, roles, grupos) y
|
|
25
|
+
* — si el `UserProfileResolver` pudo enriquecerlo — su perfil (cargo,
|
|
26
|
+
* departamento, oficina). Sin config de Graph el resolver es Noop y el
|
|
27
|
+
* perfil llega `null`: el agente trabaja solo con la identidad del token.
|
|
28
|
+
*/
|
|
29
|
+
export function buildUserContextInstructions(
|
|
30
|
+
authenticatedUser: AuthenticatedUserContextValue | undefined,
|
|
31
|
+
userProfile: EnrichedUserProfile | null,
|
|
32
|
+
): string {
|
|
33
|
+
if (!authenticatedUser) {
|
|
34
|
+
return [
|
|
35
|
+
`Eres un agente de ejemplo que demuestra el uso del contexto de usuario autenticado del hub.`,
|
|
36
|
+
`# Estado actual
|
|
37
|
+
|
|
38
|
+
NO hay un usuario autenticado en el requestContext de este request (p. ej. estás corriendo en Mastra Studio local, donde no pasa por el middleware de auth del hub).
|
|
39
|
+
|
|
40
|
+
# Qué demuestras
|
|
41
|
+
|
|
42
|
+
- Cuando el request llega autenticado a través del hub, tu requestContext trae \`authenticatedUser\` (oid, upn, nombre, grupos del IdP y roles de aplicación resueltos) y estas instrucciones cambian para incluir esa identidad.
|
|
43
|
+
- Sin usuario autenticado no tienes tools disponibles (las tools se declaran condicionales por contexto). Explica esto si te piden acciones.`,
|
|
44
|
+
responseStyle,
|
|
45
|
+
limits,
|
|
46
|
+
].join('\n\n');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const rolesLine = authenticatedUser.roles?.length
|
|
50
|
+
? `- Roles de aplicación: ${authenticatedUser.roles.join(', ')}`
|
|
51
|
+
: '- Roles de aplicación: (ninguno resuelto)';
|
|
52
|
+
|
|
53
|
+
const profileSection = userProfile
|
|
54
|
+
? `# Perfil enriquecido (UserProfileResolver)
|
|
55
|
+
|
|
56
|
+
${[
|
|
57
|
+
userProfile.displayName
|
|
58
|
+
? `- Nombre para mostrar: ${userProfile.displayName}`
|
|
59
|
+
: null,
|
|
60
|
+
userProfile.jobTitle ? `- Cargo: ${userProfile.jobTitle}` : null,
|
|
61
|
+
userProfile.department ? `- Departamento: ${userProfile.department}` : null,
|
|
62
|
+
userProfile.businessUnit
|
|
63
|
+
? `- Gerencia/Unidad: ${userProfile.businessUnit}`
|
|
64
|
+
: null,
|
|
65
|
+
userProfile.officeLocation
|
|
66
|
+
? `- Oficina: ${userProfile.officeLocation}`
|
|
67
|
+
: null,
|
|
68
|
+
]
|
|
69
|
+
.filter(Boolean)
|
|
70
|
+
.join('\n')}`
|
|
71
|
+
: `# Perfil enriquecido
|
|
72
|
+
|
|
73
|
+
No disponible en este despliegue (sin config de Microsoft Graph el resolver es Noop → perfil null). Trabaja solo con la identidad del token.`;
|
|
74
|
+
|
|
75
|
+
return [
|
|
76
|
+
`Eres un agente de ejemplo que demuestra el uso del contexto de usuario autenticado del hub.`,
|
|
77
|
+
`# Usuario autenticado en este request
|
|
78
|
+
|
|
79
|
+
- oid: ${authenticatedUser.oid}
|
|
80
|
+
- UPN: ${authenticatedUser.upn}
|
|
81
|
+
${authenticatedUser.name ? `- Nombre: ${authenticatedUser.name}` : ''}
|
|
82
|
+
${rolesLine}
|
|
83
|
+
- Grupos del IdP: ${authenticatedUser.groups.length ? authenticatedUser.groups.join(', ') : '(ninguno)'}`,
|
|
84
|
+
profileSection,
|
|
85
|
+
`# Qué demuestras
|
|
86
|
+
|
|
87
|
+
- Sabes quién es el usuario sin preguntar: la identidad llegó en el requestContext vía el middleware de auth del hub.
|
|
88
|
+
- Tienes la tool \`get-my-profile\` disponible SOLO porque hay usuario autenticado (tools condicionales por contexto). Úsala cuando el usuario pregunte por sus datos, roles o grupos.
|
|
89
|
+
- Si el usuario pide algo fuera de su identidad/perfil, explica que eres un agente de ejemplo acotado a demostrar el contexto.`,
|
|
90
|
+
responseStyle,
|
|
91
|
+
limits,
|
|
92
|
+
].join('\n\n');
|
|
93
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { createTool } from '@mastra/core/tools';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import {
|
|
4
|
+
getAuthenticatedUserOrThrow,
|
|
5
|
+
getOptionalUserProfileFromRequestContext,
|
|
6
|
+
} from '@envisiongroup/alakazam';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Tool de ejemplo self-contained (sin llamadas externas): devuelve el
|
|
10
|
+
* usuario autenticado y su perfil enriquecido tal como quedaron en el
|
|
11
|
+
* `requestContext` del request.
|
|
12
|
+
*
|
|
13
|
+
* Demuestra el patrón de leer contexto dentro de `execute` via
|
|
14
|
+
* `context.requestContext` — el mismo que usan las tools de Microsoft
|
|
15
|
+
* Graph del hub real, pero sin necesitar credenciales de Graph.
|
|
16
|
+
*/
|
|
17
|
+
export const getMyProfileTool = createTool({
|
|
18
|
+
id: 'get-my-profile',
|
|
19
|
+
description:
|
|
20
|
+
'Devuelve la identidad del usuario autenticado (oid, upn, nombre, grupos, roles de aplicación) y, si está resuelto, su perfil enriquecido (cargo, departamento, oficina).',
|
|
21
|
+
inputSchema: z.object({}),
|
|
22
|
+
outputSchema: z.object({
|
|
23
|
+
oid: z.string(),
|
|
24
|
+
upn: z.string(),
|
|
25
|
+
name: z.string().optional(),
|
|
26
|
+
groups: z.array(z.string()),
|
|
27
|
+
roles: z.array(z.string()).optional(),
|
|
28
|
+
provider: z.string().optional(),
|
|
29
|
+
profile: z
|
|
30
|
+
.object({
|
|
31
|
+
displayName: z.string().optional(),
|
|
32
|
+
jobTitle: z.string().optional(),
|
|
33
|
+
department: z.string().optional(),
|
|
34
|
+
businessUnit: z.string().optional(),
|
|
35
|
+
officeLocation: z.string().optional(),
|
|
36
|
+
})
|
|
37
|
+
.nullable(),
|
|
38
|
+
}),
|
|
39
|
+
execute: async (_input, context) => {
|
|
40
|
+
const requestContext = context?.requestContext;
|
|
41
|
+
// `OrThrow` es seguro acá: el agente solo expone esta tool cuando
|
|
42
|
+
// YA verificó que hay usuario autenticado en el requestContext
|
|
43
|
+
// (ver `getUserContextTools` en user-context-agent.ts).
|
|
44
|
+
const authenticatedUser = getAuthenticatedUserOrThrow(requestContext);
|
|
45
|
+
const profile =
|
|
46
|
+
getOptionalUserProfileFromRequestContext(requestContext) ?? null;
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
oid: authenticatedUser.oid,
|
|
50
|
+
upn: authenticatedUser.upn,
|
|
51
|
+
...(authenticatedUser.name ? { name: authenticatedUser.name } : {}),
|
|
52
|
+
groups: authenticatedUser.groups,
|
|
53
|
+
...(authenticatedUser.roles
|
|
54
|
+
? { roles: authenticatedUser.roles }
|
|
55
|
+
: {}),
|
|
56
|
+
...(authenticatedUser.provider
|
|
57
|
+
? { provider: authenticatedUser.provider }
|
|
58
|
+
: {}),
|
|
59
|
+
profile,
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Agent } from '@mastra/core/agent';
|
|
2
|
+
import type { ToolsInput } from '@mastra/core/agent';
|
|
3
|
+
import type { RequestContext } from '@mastra/core/request-context';
|
|
4
|
+
import { Memory } from '@mastra/memory';
|
|
5
|
+
import { getAzureClient } from '@envisiongroup/alakazam/services/azure-model';
|
|
6
|
+
import {
|
|
7
|
+
ensureUserProfileInRequestContext,
|
|
8
|
+
getAuthenticatedUserFromRequestContext,
|
|
9
|
+
getUserProfileResolver,
|
|
10
|
+
hubAgent,
|
|
11
|
+
optionalAuthenticatedUserRequestContextSchema,
|
|
12
|
+
} from '@envisiongroup/alakazam';
|
|
13
|
+
import { buildUserContextInstructions } from './prompts';
|
|
14
|
+
import { getMyProfileTool } from './tools/get-my-profile-tool';
|
|
15
|
+
|
|
16
|
+
// Crea el cliente de Azure y selecciona el modelo LLM que se usara.
|
|
17
|
+
const azureClient = getAzureClient();
|
|
18
|
+
|
|
19
|
+
// Asegurate que el modelo especificado exista en tu proyecto de Azure Foundry
|
|
20
|
+
const llmModel = azureClient('gpt-5.4');
|
|
21
|
+
|
|
22
|
+
// Obtiene el resolvedor encargado de cargar el perfil del usuario.
|
|
23
|
+
const userProfileResolver = getUserProfileResolver();
|
|
24
|
+
|
|
25
|
+
type RequestContextReader = Pick<RequestContext<any>, 'get'> | undefined;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Tools condicionales por contexto: sin usuario autenticado en el
|
|
29
|
+
* requestContext, el agente no expone ninguna tool. Es la demo del patrón
|
|
30
|
+
* `tools: ({ requestContext }) => ...` de Mastra.
|
|
31
|
+
*/
|
|
32
|
+
function getUserContextTools(requestContext: RequestContextReader): ToolsInput {
|
|
33
|
+
if (!getAuthenticatedUserFromRequestContext(requestContext)) {
|
|
34
|
+
return {};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return { getMyProfileTool };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Agente de ejemplo: contexto del usuario autenticado.
|
|
42
|
+
*
|
|
43
|
+
* Versión self-contained del patrón que usa `personal-profile-agent` en el
|
|
44
|
+
* hub real, SIN dependencia de Microsoft Graph (funciona out-of-the-box en
|
|
45
|
+
* un proyecto recién scaffolded):
|
|
46
|
+
*
|
|
47
|
+
* - `requestContextSchema`: declara el shape del contexto que el agente
|
|
48
|
+
* espera (usuario opcional → corre también anónimo en Studio).
|
|
49
|
+
* - `instructions` dinámicas: con usuario, resuelve el perfil via
|
|
50
|
+
* `ensureUserProfileInRequestContext` (cachea el resultado en el propio
|
|
51
|
+
* requestContext) y compone instrucciones con la identidad.
|
|
52
|
+
* - `tools` condicionales: `get-my-profile` solo existe si hay usuario.
|
|
53
|
+
*
|
|
54
|
+
* Sin env vars de Graph, `getUserProfileResolver()` devuelve un resolver
|
|
55
|
+
* Noop → el perfil enriquecido llega `null` y el agente trabaja solo con
|
|
56
|
+
* la identidad del token (oid/upn/nombre/grupos/roles).
|
|
57
|
+
*/
|
|
58
|
+
export const userContextAgent = new Agent({
|
|
59
|
+
id: 'user-context-agent',
|
|
60
|
+
name: 'Contexto de Usuario',
|
|
61
|
+
description:
|
|
62
|
+
'Agente de ejemplo que demuestra el contexto del usuario autenticado: requestContextSchema, instrucciones dinámicas con la identidad del usuario y tools condicionales según haya (o no) usuario en el requestContext.',
|
|
63
|
+
requestContextSchema: optionalAuthenticatedUserRequestContextSchema,
|
|
64
|
+
instructions: async ({ requestContext }) => {
|
|
65
|
+
if (!getAuthenticatedUserFromRequestContext(requestContext)) {
|
|
66
|
+
return buildUserContextInstructions(undefined, null);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const userProfile = await ensureUserProfileInRequestContext(
|
|
70
|
+
requestContext,
|
|
71
|
+
userProfileResolver,
|
|
72
|
+
);
|
|
73
|
+
return buildUserContextInstructions(
|
|
74
|
+
getAuthenticatedUserFromRequestContext(requestContext),
|
|
75
|
+
userProfile,
|
|
76
|
+
);
|
|
77
|
+
},
|
|
78
|
+
model: llmModel,
|
|
79
|
+
tools: ({ requestContext }) => getUserContextTools(requestContext),
|
|
80
|
+
metadata: {
|
|
81
|
+
...hubAgent({
|
|
82
|
+
catalog: { tags: ['ejemplo', 'usuario', 'contexto'] },
|
|
83
|
+
access: { public: true },
|
|
84
|
+
}),
|
|
85
|
+
},
|
|
86
|
+
memory: new Memory(),
|
|
87
|
+
}) as Agent;
|