Skip to Content
ワークフロー配列を入力として使用

配列を入力として使用する

この例では、ワークフローで配列入力を処理する方法を示します。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 };

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

ファイルサマリーワークフローを2つのステップで定義します。1つ目は特定のファイルのコードを取得するステップ、2つ目はそのコードファイルのためのreadmeを生成するステップです。

workflows/file-summary-workflow.ts
import { createWorkflow, createStep } from "@mastra/core/workflows"; 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ジェネレーターのワークフローを定義する

4つのステップからなるreadmeジェネレーターのワークフローを定義します。最初のステップはGitHubリポジトリをクローンすること、次にワークフローを一時停止してreadmeを生成する際に考慮するフォルダーをユーザーから入力してもらうこと、次にそのフォルダー内のすべてのファイルの概要を生成すること、そして最後に各ファイルごとに生成されたドキュメントを1つのreadmeにまとめることです。

import { createWorkflow, createStep } from "@mastra/core/workflows"; 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: "Please select folders to generate documentation for:", }); 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( `Generate a README.md file for the following documentation: ${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クラスでAgentとWorkflowインスタンスを登録する

エージェントとワークフローを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, }, 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.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); }