@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.
Files changed (27) hide show
  1. package/LICENSE +5 -0
  2. package/README.md +56 -0
  3. package/bin/create-alakazam.mjs +7 -0
  4. package/package.json +28 -0
  5. package/src/args.mjs +72 -0
  6. package/src/createAlakazamCli.mjs +115 -0
  7. package/src/projectName.mjs +33 -0
  8. package/src/prompts.mjs +63 -0
  9. package/src/registry.mjs +62 -0
  10. package/src/template.mjs +135 -0
  11. package/templates/default/.env.sample +55 -0
  12. package/templates/default/README.md +66 -0
  13. package/templates/default/gitignore +7 -0
  14. package/templates/default/package.json +36 -0
  15. package/templates/default/src/mastra/access/role-rules.ts +31 -0
  16. package/templates/default/src/mastra/agents/azure-claude-sandbox-agent/azure-claude-sandbox-agent.ts +108 -0
  17. package/templates/default/src/mastra/agents/azure-openai-sandbox-agent/azure-openai-sandbox-agent.ts +111 -0
  18. package/templates/default/src/mastra/agents/azure-openai-sandbox-agent/prompts/index.ts +42 -0
  19. package/templates/default/src/mastra/agents/user-context-agent/prompts/index.ts +93 -0
  20. package/templates/default/src/mastra/agents/user-context-agent/tools/get-my-profile-tool.ts +62 -0
  21. package/templates/default/src/mastra/agents/user-context-agent/user-context-agent.ts +87 -0
  22. package/templates/default/src/mastra/agents/weather-agent/weather-agent.ts +68 -0
  23. package/templates/default/src/mastra/index.ts +47 -0
  24. package/templates/default/src/mastra/scorers/weather-scorer.ts +92 -0
  25. package/templates/default/src/mastra/tools/weather-tool.ts +110 -0
  26. package/templates/default/src/mastra/workflows/weather-workflow.ts +198 -0
  27. package/templates/default/tsconfig.json +16 -0
@@ -0,0 +1,68 @@
1
+ import { Agent } from '@mastra/core/agent';
2
+ import { Memory } from '@mastra/memory';
3
+ import { hubAgent } from '@envisiongroup/alakazam';
4
+ import { weatherTool } from '../../tools/weather-tool';
5
+ import { scorers } from '../../scorers/weather-scorer';
6
+ import { getAzureClient } from '@envisiongroup/alakazam/services/azure-model';
7
+
8
+ // Crea el cliente de Azure y selecciona el modelo LLM que se usara.
9
+ const azureClient = getAzureClient();
10
+
11
+ // Asegurate que el modelo especificado exista en tu proyecto de Azure Foundry
12
+ const llmModel = azureClient('gpt-4.1-mini');
13
+
14
+ /**
15
+ * Agente de ejemplo que consulta el clima y puede sugerir actividades según
16
+ * las condiciones actuales. También muestra cómo asociar evaluadores a un
17
+ * agente para medir el uso de tools, la completitud y la traducción.
18
+ */
19
+ export const weatherAgent = new Agent({
20
+ id: 'weather-agent',
21
+ metadata: {
22
+ ...hubAgent({
23
+ catalog: { tags: ['weather', 'demo'] },
24
+ access: { public: true },
25
+ }),
26
+ },
27
+ name: 'Weather Agent',
28
+ instructions: `
29
+ You are a helpful weather assistant that provides accurate weather information and can help planning activities based on the weather.
30
+
31
+ Your primary function is to help users get weather details for specific locations. When responding:
32
+ - Always ask for a location if none is provided
33
+ - If the location name isn't in English, please translate it
34
+ - If giving a location with multiple parts (e.g. "New York, NY"), use the most relevant part (e.g. "New York")
35
+ - Include relevant details like humidity, wind conditions, and precipitation
36
+ - Keep responses concise but informative
37
+ - If the user asks for activities and provides the weather forecast, suggest activities based on the weather forecast.
38
+ - If the user asks for activities, respond in the format they request.
39
+
40
+ Use the weatherTool to fetch current weather data.
41
+ `,
42
+ model: llmModel,
43
+ tools: { weatherTool },
44
+ scorers: {
45
+ toolCallAppropriateness: {
46
+ scorer: scorers.toolCallAppropriatenessScorer,
47
+ sampling: {
48
+ type: 'ratio',
49
+ rate: 1,
50
+ },
51
+ },
52
+ completeness: {
53
+ scorer: scorers.completenessScorer,
54
+ sampling: {
55
+ type: 'ratio',
56
+ rate: 1,
57
+ },
58
+ },
59
+ translation: {
60
+ scorer: scorers.translationScorer,
61
+ sampling: {
62
+ type: 'ratio',
63
+ rate: 1,
64
+ },
65
+ },
66
+ },
67
+ memory: new Memory(),
68
+ });
@@ -0,0 +1,47 @@
1
+ import {
2
+ createAgentsHub,
3
+ entraIdentityProvider,
4
+ staticRoleResolver,
5
+ } from '@envisiongroup/alakazam';
6
+
7
+ // --- Dominio de ESTE proyecto ------------------------------------------------
8
+ import { weatherAgent } from './agents/weather-agent/weather-agent';
9
+ import { azureOpenAISandboxAgent } from './agents/azure-openai-sandbox-agent/azure-openai-sandbox-agent'; // __EXAMPLES__
10
+ import { azureClaudeSandboxAgent } from './agents/azure-claude-sandbox-agent/azure-claude-sandbox-agent'; // __EXAMPLES__
11
+ import { userContextAgent } from './agents/user-context-agent/user-context-agent'; // __EXAMPLES__
12
+ import { weatherTool } from './tools/weather-tool';
13
+ import { weatherWorkflow } from './workflows/weather-workflow';
14
+ import {
15
+ toolCallAppropriatenessScorer,
16
+ completenessScorer,
17
+ translationScorer,
18
+ } from './scorers/weather-scorer';
19
+ import { ROLE_RULES } from './access/role-rules';
20
+
21
+ // --- Hub transversal (@envisiongroup/alakazam) -------------------------------
22
+ // Todo el wiring (storage, observability, middlewares de auth/origin, CORS y
23
+ // rutas custom) lo resuelve `createAgentsHub`. Acá solo declaras tu dominio:
24
+ // agents, tools, workflows y scorers propios.
25
+ //
26
+ // El catálogo y el acceso de cada recurso se declaran en su propia
27
+ // `metadata.envisionHub` (vía `hubAgent()` / `hubWorkflow()`). Un recurso
28
+ // registrado sin declaración de acceso falla el arranque (fail-fast).
29
+ export const mastra = await createAgentsHub({
30
+ agents: {
31
+ weatherAgent,
32
+ azureOpenAISandboxAgent, // __EXAMPLES__
33
+ azureClaudeSandboxAgent, // __EXAMPLES__
34
+ userContextAgent, // __EXAMPLES__
35
+ },
36
+ tools: { weatherTool },
37
+ workflows: { weatherWorkflow },
38
+ scorers: {
39
+ toolCallAppropriatenessScorer,
40
+ completenessScorer,
41
+ translationScorer,
42
+ },
43
+ auth: {
44
+ providers: [entraIdentityProvider()],
45
+ roleResolver: staticRoleResolver(ROLE_RULES),
46
+ },
47
+ });
@@ -0,0 +1,92 @@
1
+ import { z } from 'zod';
2
+ import { createToolCallAccuracyScorerCode } from '@mastra/evals/scorers/prebuilt';
3
+ import { createCompletenessScorer } from '@mastra/evals/scorers/prebuilt';
4
+ import {
5
+ getAssistantMessageFromRunOutput,
6
+ getUserMessageFromRunInput,
7
+ } from '@mastra/evals/scorers/utils';
8
+ import { createScorer } from '@mastra/core/evals';
9
+
10
+ /** Comprueba si el agente eligió la tool de clima esperada. */
11
+ export const toolCallAppropriatenessScorer = createToolCallAccuracyScorerCode({
12
+ expectedTool: 'weatherTool',
13
+ strictMode: false,
14
+ });
15
+
16
+ /** Comprueba si la respuesta contiene la información necesaria. */
17
+ export const completenessScorer = createCompletenessScorer();
18
+
19
+ /**
20
+ * Evalúa con otro modelo si una ubicación en otro idioma se tradujo bien.
21
+ * Si la ubicación ya está en inglés, la evaluación otorga la puntuación máxima.
22
+ */
23
+ export const translationScorer = createScorer({
24
+ id: 'translation-quality-scorer',
25
+ name: 'Translation Quality',
26
+ description:
27
+ 'Checks that non-English location names are translated and used correctly',
28
+ type: 'agent',
29
+ judge: {
30
+ model: 'openai/gpt-5-mini',
31
+ instructions:
32
+ 'You are an expert evaluator of translation quality for geographic locations. ' +
33
+ 'Determine whether the user text mentions a non-English location and whether the assistant correctly uses an English translation of that location. ' +
34
+ 'Be lenient with transliteration differences and diacritics. ' +
35
+ 'Return only the structured JSON matching the provided schema.',
36
+ },
37
+ })
38
+ .preprocess(({ run }) => {
39
+ const userText = getUserMessageFromRunInput(run.input) || '';
40
+ const assistantText =
41
+ getAssistantMessageFromRunOutput(run.output) || '';
42
+ return { userText, assistantText };
43
+ })
44
+ .analyze({
45
+ description:
46
+ 'Extract location names and detect language/translation adequacy',
47
+ outputSchema: z.object({
48
+ nonEnglish: z.boolean(),
49
+ translated: z.boolean(),
50
+ confidence: z.number().min(0).max(1).default(1),
51
+ explanation: z.string().default(''),
52
+ }),
53
+ createPrompt: ({ results }) => `
54
+ You are evaluating if a weather assistant correctly handled translation of a non-English location.
55
+ User text:
56
+ """
57
+ ${results.preprocessStepResult.userText}
58
+ """
59
+ Assistant response:
60
+ """
61
+ ${results.preprocessStepResult.assistantText}
62
+ """
63
+ Tasks:
64
+ 1) Identify if the user mentioned a location that appears non-English.
65
+ 2) If non-English, check whether the assistant used a correct English translation of that location in its response.
66
+ 3) Be lenient with transliteration differences (e.g., accents/diacritics).
67
+ Return JSON with fields:
68
+ {
69
+ "nonEnglish": boolean,
70
+ "translated": boolean,
71
+ "confidence": number,
72
+ "explanation": string
73
+ }
74
+ `,
75
+ })
76
+ .generateScore(({ results }) => {
77
+ const r = (results as any)?.analyzeStepResult || {};
78
+ if (!r.nonEnglish) return 1;
79
+ if (r.translated)
80
+ return Math.max(0, Math.min(1, 0.7 + 0.3 * (r.confidence ?? 1)));
81
+ return 0;
82
+ })
83
+ .generateReason(({ results, score }) => {
84
+ const r = (results as any)?.analyzeStepResult || {};
85
+ return `Translation scoring: nonEnglish=${r.nonEnglish ?? false}, translated=${r.translated ?? false}, confidence=${r.confidence ?? 0}. Score=${score}. ${r.explanation ?? ''}`;
86
+ });
87
+
88
+ export const scorers = {
89
+ toolCallAppropriatenessScorer,
90
+ completenessScorer,
91
+ translationScorer,
92
+ };
@@ -0,0 +1,110 @@
1
+ import { createTool } from '@mastra/core/tools';
2
+ import { z } from 'zod';
3
+
4
+ interface GeocodingResponse {
5
+ results: {
6
+ latitude: number;
7
+ longitude: number;
8
+ name: string;
9
+ }[];
10
+ }
11
+ interface WeatherResponse {
12
+ current: {
13
+ time: string;
14
+ temperature_2m: number;
15
+ apparent_temperature: number;
16
+ relative_humidity_2m: number;
17
+ wind_speed_10m: number;
18
+ wind_gusts_10m: number;
19
+ weather_code: number;
20
+ };
21
+ }
22
+
23
+ /**
24
+ * Consulta el clima actual de una ubicación usando Open-Meteo.
25
+ *
26
+ * Primero convierte el nombre de la ciudad en coordenadas y después obtiene
27
+ * la temperatura, la humedad, el viento y las condiciones actuales.
28
+ */
29
+ export const weatherTool = createTool({
30
+ id: 'get-weather',
31
+ description: 'Get current weather for a location',
32
+ inputSchema: z.object({
33
+ location: z.string().describe('City name'),
34
+ }),
35
+ outputSchema: z.object({
36
+ temperature: z.number(),
37
+ feelsLike: z.number(),
38
+ humidity: z.number(),
39
+ windSpeed: z.number(),
40
+ windGust: z.number(),
41
+ conditions: z.string(),
42
+ location: z.string(),
43
+ }),
44
+ execute: async (inputData) => {
45
+ return await getWeather(inputData.location);
46
+ },
47
+ });
48
+
49
+ /** Obtiene los datos del clima después de resolver la ubicación. */
50
+ const getWeather = async (location: string) => {
51
+ const geocodingUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(location)}&count=1`;
52
+ const geocodingResponse = await fetch(geocodingUrl);
53
+ const geocodingData = (await geocodingResponse.json()) as GeocodingResponse;
54
+
55
+ if (!geocodingData.results?.[0]) {
56
+ throw new Error(`Location '${location}' not found`);
57
+ }
58
+
59
+ const { latitude, longitude, name } = geocodingData.results[0];
60
+
61
+ const weatherUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&current=temperature_2m,apparent_temperature,relative_humidity_2m,wind_speed_10m,wind_gusts_10m,weather_code`;
62
+
63
+ const response = await fetch(weatherUrl);
64
+ const data = (await response.json()) as WeatherResponse;
65
+
66
+ return {
67
+ temperature: data.current.temperature_2m,
68
+ feelsLike: data.current.apparent_temperature,
69
+ humidity: data.current.relative_humidity_2m,
70
+ windSpeed: data.current.wind_speed_10m,
71
+ windGust: data.current.wind_gusts_10m,
72
+ conditions: getWeatherCondition(data.current.weather_code),
73
+ location: name,
74
+ };
75
+ };
76
+
77
+ /** Convierte el código meteorológico de Open-Meteo en texto legible. */
78
+ function getWeatherCondition(code: number): string {
79
+ const conditions: Record<number, string> = {
80
+ 0: 'Clear sky',
81
+ 1: 'Mainly clear',
82
+ 2: 'Partly cloudy',
83
+ 3: 'Overcast',
84
+ 45: 'Foggy',
85
+ 48: 'Depositing rime fog',
86
+ 51: 'Light drizzle',
87
+ 53: 'Moderate drizzle',
88
+ 55: 'Dense drizzle',
89
+ 56: 'Light freezing drizzle',
90
+ 57: 'Dense freezing drizzle',
91
+ 61: 'Slight rain',
92
+ 63: 'Moderate rain',
93
+ 65: 'Heavy rain',
94
+ 66: 'Light freezing rain',
95
+ 67: 'Heavy freezing rain',
96
+ 71: 'Slight snow fall',
97
+ 73: 'Moderate snow fall',
98
+ 75: 'Heavy snow fall',
99
+ 77: 'Snow grains',
100
+ 80: 'Slight rain showers',
101
+ 81: 'Moderate rain showers',
102
+ 82: 'Violent rain showers',
103
+ 85: 'Slight snow showers',
104
+ 86: 'Heavy snow showers',
105
+ 95: 'Thunderstorm',
106
+ 96: 'Thunderstorm with slight hail',
107
+ 99: 'Thunderstorm with heavy hail',
108
+ };
109
+ return conditions[code] || 'Unknown';
110
+ }
@@ -0,0 +1,198 @@
1
+ import { createStep, createWorkflow } from '@mastra/core/workflows';
2
+ import { hubWorkflow } from '@envisiongroup/alakazam';
3
+ import { z } from 'zod';
4
+
5
+ /** Datos que se comparten entre los pasos del workflow. */
6
+ const forecastSchema = z.object({
7
+ date: z.string(),
8
+ maxTemp: z.number(),
9
+ minTemp: z.number(),
10
+ precipitationChance: z.number(),
11
+ condition: z.string(),
12
+ location: z.string(),
13
+ });
14
+
15
+ /** Convierte un código de Open-Meteo en una condición legible. */
16
+ function getWeatherCondition(code: number): string {
17
+ const conditions: Record<number, string> = {
18
+ 0: 'Clear sky',
19
+ 1: 'Mainly clear',
20
+ 2: 'Partly cloudy',
21
+ 3: 'Overcast',
22
+ 45: 'Foggy',
23
+ 48: 'Depositing rime fog',
24
+ 51: 'Light drizzle',
25
+ 53: 'Moderate drizzle',
26
+ 55: 'Dense drizzle',
27
+ 61: 'Slight rain',
28
+ 63: 'Moderate rain',
29
+ 65: 'Heavy rain',
30
+ 71: 'Slight snow fall',
31
+ 73: 'Moderate snow fall',
32
+ 75: 'Heavy snow fall',
33
+ 95: 'Thunderstorm',
34
+ };
35
+ return conditions[code] || 'Unknown';
36
+ }
37
+
38
+ /** Busca la ubicación y obtiene el pronóstico del clima. */
39
+ const fetchWeather = createStep({
40
+ id: 'fetch-weather',
41
+ description: 'Fetches weather forecast for a given city',
42
+ inputSchema: z.object({
43
+ city: z.string().describe('The city to get the weather for'),
44
+ }),
45
+ outputSchema: forecastSchema,
46
+ execute: async ({ inputData }) => {
47
+ if (!inputData) {
48
+ throw new Error('Input data not found');
49
+ }
50
+
51
+ const geocodingUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(inputData.city)}&count=1`;
52
+ const geocodingResponse = await fetch(geocodingUrl);
53
+ const geocodingData = (await geocodingResponse.json()) as {
54
+ results: { latitude: number; longitude: number; name: string }[];
55
+ };
56
+
57
+ if (!geocodingData.results?.[0]) {
58
+ throw new Error(`Location '${inputData.city}' not found`);
59
+ }
60
+
61
+ const { latitude, longitude, name } = geocodingData.results[0];
62
+
63
+ const weatherUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&current=precipitation,weathercode&timezone=auto,&hourly=precipitation_probability,temperature_2m`;
64
+ const response = await fetch(weatherUrl);
65
+ const data = (await response.json()) as {
66
+ current: {
67
+ time: string;
68
+ precipitation: number;
69
+ weathercode: number;
70
+ };
71
+ hourly: {
72
+ precipitation_probability: number[];
73
+ temperature_2m: number[];
74
+ };
75
+ };
76
+
77
+ const forecast = {
78
+ date: new Date().toISOString(),
79
+ maxTemp: Math.max(...data.hourly.temperature_2m),
80
+ minTemp: Math.min(...data.hourly.temperature_2m),
81
+ condition: getWeatherCondition(data.current.weathercode),
82
+ precipitationChance: data.hourly.precipitation_probability.reduce(
83
+ (acc, curr) => Math.max(acc, curr),
84
+ 0,
85
+ ),
86
+ location: name,
87
+ };
88
+
89
+ return forecast;
90
+ },
91
+ });
92
+
93
+ /** Usa el agente del clima para proponer actividades adecuadas. */
94
+ const planActivities = createStep({
95
+ id: 'plan-activities',
96
+ description: 'Suggests activities based on weather conditions',
97
+ inputSchema: forecastSchema,
98
+ outputSchema: z.object({
99
+ activities: z.string(),
100
+ }),
101
+ execute: async ({ inputData, mastra }) => {
102
+ const forecast = inputData;
103
+
104
+ if (!forecast) {
105
+ throw new Error('Forecast data not found');
106
+ }
107
+
108
+ const agent = mastra?.getAgent('weatherAgent');
109
+ if (!agent) {
110
+ throw new Error('Weather agent not found');
111
+ }
112
+
113
+ const prompt = `Based on the following weather forecast for ${forecast.location}, suggest appropriate activities:
114
+ ${JSON.stringify(forecast, null, 2)}
115
+ For each day in the forecast, structure your response exactly as follows:
116
+
117
+ 📅 [Day, Month Date, Year]
118
+ ═══════════════════════════
119
+
120
+ 🌡️ WEATHER SUMMARY
121
+ • Conditions: [brief description]
122
+ • Temperature: [X°C/Y°F to A°C/B°F]
123
+ • Precipitation: [X% chance]
124
+
125
+ 🌅 MORNING ACTIVITIES
126
+ Outdoor:
127
+ • [Activity Name] - [Brief description including specific location/route]
128
+ Best timing: [specific time range]
129
+ Note: [relevant weather consideration]
130
+
131
+ 🌞 AFTERNOON ACTIVITIES
132
+ Outdoor:
133
+ • [Activity Name] - [Brief description including specific location/route]
134
+ Best timing: [specific time range]
135
+ Note: [relevant weather consideration]
136
+
137
+ 🏠 INDOOR ALTERNATIVES
138
+ • [Activity Name] - [Brief description including specific venue]
139
+ Ideal for: [weather condition that would trigger this alternative]
140
+
141
+ ⚠️ SPECIAL CONSIDERATIONS
142
+ • [Any relevant weather warnings, UV index, wind conditions, etc.]
143
+
144
+ Guidelines:
145
+ - Suggest 2-3 time-specific outdoor activities per day
146
+ - Include 1-2 indoor backup options
147
+ - For precipitation >50%, lead with indoor activities
148
+ - All activities must be specific to the location
149
+ - Include specific venues, trails, or locations
150
+ - Consider activity intensity based on temperature
151
+ - Keep descriptions concise but informative
152
+
153
+ Maintain this exact formatting for consistency, using the emoji and section headers as shown.`;
154
+
155
+ const response = await agent.stream([
156
+ {
157
+ role: 'user',
158
+ content: prompt,
159
+ },
160
+ ]);
161
+
162
+ let activitiesText = '';
163
+
164
+ for await (const chunk of response.textStream) {
165
+ process.stdout.write(chunk);
166
+ activitiesText += chunk;
167
+ }
168
+
169
+ return {
170
+ activities: activitiesText,
171
+ };
172
+ },
173
+ });
174
+
175
+ /**
176
+ * Workflow de ejemplo con dos pasos: obtiene el clima y propone actividades.
177
+ */
178
+ const weatherWorkflow = createWorkflow({
179
+ id: 'weather-workflow',
180
+ metadata: {
181
+ ...hubWorkflow({
182
+ catalog: { tags: ['weather', 'demo'] },
183
+ access: { public: true },
184
+ }),
185
+ },
186
+ inputSchema: z.object({
187
+ city: z.string().describe('The city to get the weather for'),
188
+ }),
189
+ outputSchema: z.object({
190
+ activities: z.string(),
191
+ }),
192
+ })
193
+ .then(fetchWeather)
194
+ .then(planActivities);
195
+
196
+ weatherWorkflow.commit();
197
+
198
+ export { weatherWorkflow };
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ES2022",
5
+ "moduleResolution": "bundler",
6
+ "allowImportingTsExtensions": true,
7
+ "esModuleInterop": true,
8
+ "forceConsistentCasingInFileNames": true,
9
+ "strict": true,
10
+ "types": ["node"],
11
+ "skipLibCheck": true,
12
+ "noEmit": true,
13
+ "outDir": "dist"
14
+ },
15
+ "include": ["src/**/*"]
16
+ }