Skip to Content
Workflows Vnext配列を入力として

配列を入力として

この例では、ワークフローで配列入力を処理する方法を示しています。Mastraは、配列内の各アイテムに対してステップを実行する.foreach()ヘルパー関数を提供しています。

セットアップ

npm install @ai-sdk/openai @mastra/core simple-git

ドキュメント生成エージェントの定義

コードファイルまたはコードファイルの要約を与えられたときに、LLM呼び出しを活用してドキュメントを生成するドキュメント生成エージェントを定義します。

agents/docs-generator-agent.ts
import { Agent } from "@mastra/core/agent"; import { openai } from "@ai-sdk/openai"; // Create a documentation generator agent for code analysis const docGeneratorAgent = new Agent({ name: "doc_generator_agent", instructions: `You are a technical documentation expert. You will analyze the provided code files and generate a comprehensive documentation summary. For each file: 1. Identify the main purpose and functionality 2. Document key components, classes, functions, and interfaces 3. Note important dependencies and relationships between components 4. Highlight any notable patterns or architectural decisions 5. Include relevant code examples where helpful Format the documentation in a clear, organized manner using markdown with: - File overviews - Component breakdowns - Code examples - Cross-references between related components Focus on making the documentation clear and useful for developers who need to understand and work with this codebase.`, model: openai("gpt-4o"), }); export { docGeneratorAgent };

ファイルサマリーワークフローの定義

特定のファイルのコードを取得するステップと、そのコードファイルのREADMEを生成する別のステップという2つのステップでファイルサマリーワークフローを定義します。

workflows/file-summary-workflow.ts
import { createWorkflow, createStep } from "@mastra/core/workflows/vNext"; import { docGeneratorAgent } from "../agents/docs-generator-agent"; import { z } from "zod"; import fs from "fs"; // Step 1: Read the code content from a file const scrapeCodeStep = createStep({ id: "scrape_code", description: "Scrape the code from a single file", inputSchema: z.string(), outputSchema: z.object({ path: z.string(), content: z.string(), }), execute: async ({ inputData }) => { const filePath = inputData; const content = fs.readFileSync(filePath, "utf-8"); return { path: filePath, content, }; }, }); // Step 2: Generate documentation for a single file const generateDocForFileStep = createStep({ id: "generateDocForFile", inputSchema: z.object({ path: z.string(), content: z.string(), }), outputSchema: z.object({ path: z.string(), documentation: z.string(), }), execute: async ({ inputData }) => { const docs = await docGeneratorAgent.generate( `Generate documentation for the following code: ${inputData.content}`, ); return { path: inputData.path, documentation: docs.text.toString(), }; }, }); const generateSummaryWorkflow = createWorkflow({ id: "generate-summary", inputSchema: z.string(), outputSchema: z.object({ path: z.string(), documentation: z.string(), }), steps: [scrapeCodeStep, generateDocForFileStep], }) .then(scrapeCodeStep) .then(generateDocForFileStep) .commit(); export { generateSummaryWorkflow };

READMEジェネレーターワークフローの定義

READMEジェネレーターワークフローを4つのステップで定義します:1つはGitHubリポジトリをクローンするステップ、1つはワークフローを一時停止してREADME生成時に考慮するフォルダに関するユーザー入力を取得するステップ、1つはフォルダ内のすべてのファイルの要約を生成するステップ、そして最後に各ファイルに対して生成されたすべてのドキュメントを1つのREADMEにまとめるステップです。

import { createWorkflow, createStep } from "@mastra/core/workflows/vNext"; import { docGeneratorAgent } from "../agents/docs-generator-agent"; import { generateSummaryWorkflow } from "./file-summary-workflow"; import { z } from "zod"; import simpleGit from "simple-git"; import fs from "fs"; import path from "path"; // Step 1: Clone a GitHub repository locally const cloneRepositoryStep = createStep({ id: "clone_repository", description: "Clone the repository from the given URL", inputSchema: z.object({ repoUrl: z.string(), }), outputSchema: z.object({ success: z.boolean(), message: z.string(), data: z.object({ repoUrl: z.string(), }), }), execute: async ({ inputData, mastra, getStepResult, getInitData, runtimeContext, }) => { const git = simpleGit(); // Skip cloning if repo already exists if (fs.existsSync("./temp")) { return { success: true, message: "Repository already exists", data: { repoUrl: inputData.repoUrl, }, }; } try { // Clone the repository to the ./temp directory await git.clone(inputData.repoUrl, "./temp"); return { success: true, message: "Repository cloned successfully", data: { repoUrl: inputData.repoUrl, }, }; } catch (error) { throw new Error(`Failed to clone repository: ${error}`); } }, }); // Step 2: Get user input on which folders to analyze const selectFolderStep = createStep({ id: "select_folder", description: "Select the folder(s) to generate the docs", inputSchema: z.object({ success: z.boolean(), message: z.string(), data: z.object({ repoUrl: z.string(), }), }), outputSchema: z.array(z.string()), suspendSchema: z.object({ folders: z.array(z.string()), message: z.string(), }), resumeSchema: z.object({ selection: z.array(z.string()), }), execute: async ({ resumeData, suspend }) => { const tempPath = "./temp"; const folders = fs .readdirSync(tempPath) .filter((item) => fs.statSync(path.join(tempPath, item)).isDirectory()); if (!resumeData?.selection) { await suspend({ folders, message: "ドキュメントを生成するフォルダを選択してください:", }); return []; } // Gather all file paths from selected folders const filePaths: string[] = []; // Helper function to recursively read files from directories const readFilesRecursively = (dir: string) => { const items = fs.readdirSync(dir); for (const item of items) { const fullPath = path.join(dir, item); const stat = fs.statSync(fullPath); if (stat.isDirectory()) { readFilesRecursively(fullPath); } else if (stat.isFile()) { filePaths.push(fullPath.replace(tempPath + "/", "")); } } }; for (const folder of resumeData.selection) { readFilesRecursively(path.join(tempPath, folder)); } return filePaths; }, }); // Step 4: Combine all documentation into a single README const collateDocumentationStep = createStep({ id: "collate_documentation", inputSchema: z.array( z.object({ path: z.string(), documentation: z.string(), }), ), outputSchema: z.string(), execute: async ({ inputData }) => { const readme = await docGeneratorAgent.generate( `以下のドキュメントに基づいてREADME.mdファイルを生成してください: ${inputData.map((doc) => doc.documentation).join("\n")}`, ); return readme.text.toString(); }, }); const readmeGeneratorWorkflow = createWorkflow({ id: "readme-generator", inputSchema: z.object({ repoUrl: z.string(), }), outputSchema: z.object({ success: z.boolean(), message: z.string(), data: z.object({ repoUrl: z.string(), }), }), steps: [ cloneRepositoryStep, selectFolderStep, generateSummaryWorkflow, collateDocumentationStep, ], }) .then(cloneRepositoryStep) .then(selectFolderStep) .foreach(generateSummaryWorkflow) .then(collateDocumentationStep) .commit(); export { readmeGeneratorWorkflow };

Mastraクラスでエージェントとワークフローのインスタンスを登録する

エージェントとワークフローをmastraインスタンスに登録します。これはワークフロー内でエージェントへのアクセスを可能にするために重要です。

index.ts
import { Mastra } from "@mastra/core"; import { PinoLogger } from "@mastra/loggers"; import { docGeneratorAgent } from "./agents/docs-generator-agent"; import { readmeGeneratorWorkflow } from "./workflows/readme-generator-workflow"; import { generateSummaryWorkflow } from "./workflows/file-summary-workflow"; // Create a new Mastra instance and register components const mastra = new Mastra({ agents: { docGeneratorAgent, }, vnext_workflows: { readmeGeneratorWorkflow, generateSummaryWorkflow, }, logger: new PinoLogger({ name: "Mastra", level: "info", }), }); export { mastra };

Readme Generator ワークフローを実行する

ここでは、mastraインスタンスからreadme generatorワークフローを取得し、実行を作成して、必要なinputDataで作成した実行を実行します。

exec.ts
import { promptUserForFolders } from "./utils"; import { mastra } from "./"; // GitHub repository to generate documentation for const ghRepoUrl = "https://github.com/mastra-ai/mastra"; const run = mastra.vnext_getWorkflow("readmeGeneratorWorkflow").createRun(); // Start the workflow with the repository URL as input const res = await run.start({ inputData: { repoUrl: ghRepoUrl } }); const { status, steps } = res; // Handle suspended workflow (waiting for user input) if (status === "suspended") { // Get the suspended step data const suspendedStep = steps["select_folder"]; let folderList: string[] = []; // Extract the folder list from step data if ( suspendedStep.status === "suspended" && "folders" in suspendedStep.payload ) { folderList = suspendedStep.payload.folders as string[]; } else if (suspendedStep.status === "success" && suspendedStep.output) { folderList = suspendedStep.output; } if (!folderList.length) { console.log("No folders available for selection."); process.exit(1); } // Prompt user to select folders const folders = await promptUserForFolders(folderList); // Resume the workflow with user selections const resumedResult = await run.resume({ resumeData: { selection: folders }, step: "select_folder", }); // Print resumed result if (resumedResult.status === "success") { console.log(resumedResult.result); } else { console.log(resumedResult); } process.exit(1); } // Handle completed workflow if (res.status === "success") { console.log(res.result ?? res); } else { console.log(res); }