|
1 | 1 | --- |
2 | 2 | trigger: model_decision |
3 | 3 | description: Mastra Conventions |
| 4 | +name: conventions |
| 5 | +title: Mastra Code Conventions |
| 6 | +tags: |
| 7 | + - conventions |
| 8 | + - coding-standards |
| 9 | + - best-practices |
4 | 10 | --- |
5 | | - |
6 | | -# Code Conventions |
| 11 | +Code Conventions |
7 | 12 |
|
8 | 13 | ## Tool Implementation Pattern |
9 | 14 |
|
10 | 15 | All tools follow this structure: |
11 | 16 |
|
12 | 17 | ```typescript |
| 18 | +import type { InferUITool} from "@mastra/core/tools"; |
13 | 19 | import { createTool } from "@mastra/core/tools"; |
14 | 20 | import { z } from "zod"; |
15 | | -import { AISpanType } from "@mastra/core/ai-tracing"; |
| 21 | +import { AISpanType, InternalSpans } from "@mastra/core/ai-tracing"; |
16 | 22 | import type { RuntimeContext } from "@mastra/core/runtime-context"; |
17 | 23 | import { log } from "../config/logger"; |
| 24 | +import type { RuntimeContext } from '@mastra/core/runtime-context' |
| 25 | +import type { TracingContext } from '@mastra/core/ai-tracing'; |
| 26 | + |
| 27 | +// Define the Zod schema for the runtime context |
| 28 | +const weatherToolContextSchema = z.object({ |
| 29 | + temperatureUnit: z.enum(['celsius', 'fahrenheit']).default('celsius'), |
| 30 | +}) |
| 31 | + |
| 32 | +// Infer the TypeScript type from the Zod schema |
| 33 | +export type WeatherToolContext = z.infer<typeof weatherToolContextSchema> |
| 34 | + |
| 35 | +export const weatherTool = createTool({ |
| 36 | + id: 'get-weather', |
| 37 | + description: 'Get current weather for a location', |
| 38 | + inputSchema: z.object({ |
| 39 | + location: z.string().describe('City name'), |
| 40 | + }), |
| 41 | + outputSchema: z.object({ |
| 42 | + temperature: z.number(), |
| 43 | + feelsLike: z.number(), |
| 44 | + humidity: z.number(), |
| 45 | + windSpeed: z.number(), |
| 46 | + windGust: z.number(), |
| 47 | + conditions: z.string(), |
| 48 | + location: z.string(), |
| 49 | + unit: z.string(), // Add unit to output schema |
| 50 | + }), |
| 51 | + execute: async ({ context, writer, runtimeContext, tracingContext }) => { |
| 52 | + await writer?.custom({ type: 'data-tool-progress', data: { message: `🚀 Starting weather lookup for ${context.location}` } }); |
| 53 | + |
| 54 | + const { temperatureUnit } = weatherToolContextSchema.parse( |
| 55 | + runtimeContext.get('weatherToolContext') |
| 56 | + ) |
| 57 | + |
| 58 | + log.info( |
| 59 | + `Fetching weather for location: ${context.location} in ${temperatureUnit}` |
| 60 | + ) |
| 61 | + |
| 62 | + const weatherSpan = tracingContext?.currentSpan?.createChildSpan({ |
| 63 | + type: AISpanType.TOOL_CALL, |
| 64 | + name: 'weather-tool', |
| 65 | + input: { location: context.location, temperatureUnit }, |
| 66 | + tracingPolicy: { internal: InternalSpans.ALL }, |
| 67 | + runtimeContext: runtimeContext as RuntimeContext<WeatherToolContext>, |
| 68 | + }) |
18 | 69 |
|
19 | | -export const myTool = createTool({ |
20 | | - id: "my-tool", |
21 | | - description: "Clear description of what the tool does", |
22 | | - inputSchema: z.object({ |
23 | | - param: z.string().describe("Parameter description") |
24 | | - }), |
25 | | - outputSchema: z.object({ |
26 | | - data: z.any().describe("Result data"), |
27 | | - error: z.string().optional() |
28 | | - }), |
29 | | - execute: async ({ context, tracingContext, runtimeContext }) => { |
30 | | - const startTime = Date.now(); |
31 | | - |
32 | | - // Create root tracing span |
33 | | - const rootSpan = tracingContext?.currentSpan?.createChildSpan({ |
34 | | - type: AISpanType.TOOL_CALL, |
35 | | - name: 'my-tool', |
36 | | - input: context, |
37 | | - }); |
38 | | - |
39 | | - try { |
40 | | - // Tool logic here |
41 | | - log.info('my-tool executed', { context }); |
42 | | - |
43 | | - rootSpan?.end({ output: result }); |
44 | | - return result; |
45 | | - } catch (error) { |
46 | | - log.error('my-tool error', { error, context }); |
47 | | - rootSpan?.error({ error }); |
48 | | - return { data: null, error: errorMessage }; |
49 | | - } |
50 | | - } |
51 | | -}); |
| 70 | + try { |
| 71 | + await writer?.custom({ type: 'data-tool-progress', data: { message: '📍 Geocoding location...' } }); |
| 72 | + const result = await getWeather(context.location, temperatureUnit) |
| 73 | + await writer?.custom({ type: 'data-tool-progress', data: { message: '🌤️ Processing weather data...' } }); |
| 74 | + weatherSpan?.end({ output: result }) |
| 75 | + log.info(`Weather fetched successfully for ${context.location}`) |
| 76 | + const finalResult = { |
| 77 | + ...result, |
| 78 | + unit: temperatureUnit === 'celsius' ? '°C' : '°F', |
| 79 | + }; |
| 80 | + await writer?.custom({ type: 'data-tool-progress', data: { message: `✅ Weather ready: ${finalResult.temperature}${finalResult.unit} in ${finalResult.location}` } }); |
| 81 | + return finalResult; |
| 82 | + } catch (error) { |
| 83 | + const errorMessage = |
| 84 | + error instanceof Error ? error.message : String(error) |
| 85 | + await writer?.custom({ type: 'data-tool-progress', data: { message: `❌ Weather error: ${errorMessage}` } }); |
| 86 | + weatherSpan?.end({ metadata: { error: errorMessage } }) |
| 87 | + log.error( |
| 88 | + `Failed to fetch weather for ${context.location}: ${errorMessage}` |
| 89 | + ) |
| 90 | + throw error |
| 91 | + } |
| 92 | + }, |
| 93 | +}) |
| 94 | +export type WeatherUITool = InferUITool<typeof weatherTool>; |
52 | 95 | ``` |
53 | 96 |
|
54 | 97 | ## Key Patterns |
|
0 commit comments