agentic-development について
複数ステップのタスクを実行する自律型 AI エージェントの設計ガイドです。言語別のフレームワーク選択に加え、計画、実行、検証のパターンを紹介します。
主な機能
- 01言語別のフレームワーク選択
- 02探索・計画・実行・検証のワークフロー
- 03ツールの定義と実装パターン
- 04マルチエージェントの連携方法
- 05メモリーと状態の管理
- 06ガードレールとエージェントのテスト
活用シーン
- 01複数ステップのエージェントワークフローの設計
- 02Python または Node.js のフレームワーク選定
- 03専用ツールとエージェントの追加
- 04エージェントの判断と出力のテスト
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ alinaqi/ maggy --skill agentic-developmentインストール後、Claude Code で/agentic-developmentを入力して読み込まれたか確認します。
詳細
エージェント開発スキル
ツールを使って複数ステップのタスクを実行する自律型 AI エージェントを構築します。
出典: Claude Agent SDK | Anthropic Claude Code のベストプラクティス | Pydantic AI | Google Gemini エージェント開発 | OpenAI エージェント構築
言語別のフレームワーク選定
| 言語/フレームワーク | デフォルト | 理由 |
|---|---|---|
| Python | Pydantic AI | 型安全、Pydantic による検証、複数モデル対応、本番環境に対応 |
| Node.js / Next.js | Claude Agent SDK | Anthropic 公式 SDK、ツール、複数エージェント、ネイティブストリーミング |
Python:Pydantic AI(デフォルト)
from pydantic_ai import Agent
from pydantic import BaseModel
class SearchResult(BaseModel):
title: str
url: str
summary: str
agent = Agent(
'claude-sonnet-4-6',
result_type=list[SearchResult],
system_prompt='You are a research assistant.',
)
# Type-safe result
result = await agent.run('Find articles about AI agents')
for item in result.data:
print(f"{item.title}: {item.url}")
Node.js / Next.js:Claude Agent SDK(デフォルト)
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
// Define tools
const tools: Anthropic.Tool[] = [
{
name: "web_search",
description: "Search the web for information",
input_schema: {
type: "object",
properties: {
query: { type: "string", description: "Search query" },
},
required: ["query"],
},
},
];
// Agentic loop
async function runAgent(prompt: string) {
const messages: Anthropic.MessageParam[] = [
{ role: "user", content: prompt },
];
while (true) {
const response = await client.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 4096,
tools,
messages,
});
// Check for tool use
if (response.stop_reason === "tool_use") {
const toolUse = response.content.find((b) => b.type === "tool_use");
if (toolUse) {
const result = await executeTool(toolUse.name, toolUse.input);
messages.push({ role: "assistant", content: response.content });
messages.push({
role: "user",
content: [{ type: "tool_result", tool_use_id: toolUse.id, content: result }],
});
continue;
}
}
// Done - return final response
return response.content.find((b) => b.type === "text")?.text;
}
}
中核となる原則
まず計画し、段階的に実行し、常に検証します。
実行前に調査と計画を行うエージェントは、すぐに行動に移すエージェントよりも一貫して優れた成果を上げます。複雑なタスクを検証可能なステップに分割し、ツールを適切に使い、実行中は明確な状態を維持します。
エージェントのアーキテクチャ
3 つの構成要素(OpenAI)
┌─────────────────────────────────────────────────┐
│ AGENT │
├─────────────────────────────────────────────────┤
│ Model (Brain) │ LLM for reasoning & │
│ │ decision-making │
├─────────────────────┼───────────────────────────┤
│ Tools (Arms/Legs) │ APIs, functions, external │
│ │ systems for action │
├─────────────────────┼───────────────────────────┤
│ Instructions │ System prompts defining │
│ (Rules) │ behavior & boundaries │
└─────────────────────┴───────────────────────────┘
プロジェクト構成
project/
├── src/
│ ├── agents/
│ │ ├── orchestrator.ts # Main agent coordinator
│ │ ├── specialized/ # Task-specific agents
│ │ │ ├── researcher.ts
│ │ │ ├── coder.ts
│ │ │ └── reviewer.ts
│ │ └── base.ts # Shared agent interface
│ ├── tools/
│ │ ├── definitions/ # Tool schemas
│ │ ├── implementations/ # Tool logic
│ │ └── registry.ts # Tool discovery
│ ├── prompts/
│ │ ├── system/ # Agent instructions
│ │ └── templates/ # Task templates
│ └── memory/
│ ├── conversation.ts # Short-term context
│ └── persistent.ts # Long-term storage
├── tests/
│ ├── agents/ # Agent behavior tests
│ ├── tools/ # Tool unit tests
│ └── evals/ # End-to-end evaluations
└── skills/ # Agent skills (Anthropic pattern)
├── skill-name/
│ ├── instructions.md
│ ├── scripts/
│ └── resources/
ワークフローパターン:探索・計画・実行・検証
1. 探索フェーズ
// Gather context before acting
async function explore(task: Task): Promise<Context> {
const relevantFiles = await agent.searchCodebase(task.query);
const existingPatterns = await agent.analyzePatterns(relevantFiles);
const dependencies = await agent.identifyDependencies(task);
return { relevantFiles, existingPatterns, dependencies };
}
2. 計画フェーズ(重要)
// Plan explicitly before execution
async function plan(task: Task, context: Context): Promise<Plan> {
const prompt = `
Task: ${task.description}
Context: ${JSON.stringify(context)}
Create a step-by-step plan. For each step:
1. What action to take
2. What tools to use
3. How to verify success
4. What could go wrong
Output JSON with steps array.
`;
return await llmCall({ prompt, schema: PlanSchema });
}
3. 実行フェーズ
// Execute with verification at each step
async function execute(plan: Plan): Promise<Result[]> {
const results: Result[] = [];
for (const step of plan.steps) {
// Execute single step
const result = await executeStep(step);
// Verify before continuing
if (!await verify(step, result)) {
// Self-correct or escalate
const corrected = await selfCorrect(step, result);
if (!corrected.success) {
return handleFailure(step, results);
}
}
results.push(result);
}
return results;
}
4. 検証フェーズ
// Independent verification prevents overfitting
async function verify(step: Step, result: Result): Promise<boolean> {
// Run tests if available
if (step.testCommand) {
const testResult = await runCommand(step.testCommand);
if (!testResult.success) return false;
}
// Use LLM to verify against criteria
const verification = await llmCall({
prompt: `
Step: ${step.description}
Expected: ${step.successCriteria}
Actual: ${JSON.stringify(result)}
Does the result satisfy the success criteria?
Respond with { "passes": boolean, "reasoning": string }
`,
schema: VerificationSchema
});
return verification.passes;
}
ツール設計
ツール定義のパターン
// tools/definitions/file-operations.ts
import { z } from 'zod';
export const ReadFileTool = {
name: 'read_file',
description: 'Read contents of a file. Use before modifying any file.',
parameters: z.object({
path: z.string().describe('Absolute path to the file'),
startLine: z.number().optional().describe('Start line (1-indexed)'),
endLine: z.number().optional().describe('End line (1-indexed)'),
}),
// Risk level for guardrails (OpenAI pattern)
riskLevel: 'low' as const,
};
export const WriteFileTool = {
name: 'write_file',
description: 'Write content to a file. Always read first to understand context.',
parameters: z.object({
path: z.string().describe('Absolute path to the file'),
content: z.string().describe('Complete file content'),
}),
riskLevel: 'medium' as const,
// Require confirmation for high-risk operations
requiresConfirmation: true,
};
ツールの実装
// tools/implementations/file-operations.ts
export async function readFile(
params: z.infer<typeof ReadFileTool.parameters>
): Promise<ToolResult> {
try {
const content = await fs.readFile(params.path, 'utf-8');
const lines = content.split('\n');
const start = (params.startLine ?? 1) - 1;
const end = params.endLine ?? lines.length;
return {
success: true,
data: lines.slice(start, end).join('\n'),
metadata: { totalLines: lines.length }
};
} catch (error) {
return {
success: false,
error: `Failed to read file: ${error.message}`
};
}
}
組み込みツールを優先する(OpenAI)
// Use platform-provided tools when available
const agent = createAgent({
tools: [
// Built-in tools (handled by platform)
{ type: 'web_search' },
{ type: 'code_interpreter' },
// Custom tools only when needed
{ type: 'function', function: customDatabaseTool },
],
});
複数エージェントのパターン
単一エージェント(デフォルト)
ほとんどのタスクには 1 つのエージェントを使います。複数のエージェントを使うと複雑さが増します。
エージェントをツールとして使うパターン(OpenAI)
// Expose specialized agents as callable tools
const researchAgent = createAgent({
name: 'researcher',
instructions: 'You research topics and return structured findings.',
tools: [webSearchTool, documentReadTool],
});
const mainAgent = createAgent({
tools: [
{
type: 'function',
function: {
name: 'research_topic',
description: 'Delegate research to specialized agent',
parameters: ResearchQuerySchema,
handler: async (query) => researchAgent.run(query),
},
},
],
});
ハンドオフパターン(OpenAI)
// One-way transfer between agents
const customerServiceAgent = createAgent({
tools: [
// Handoff to specialist when needed
{
name: 'transfer_to_billing',
description: 'Transfer to billing specialist for payment issues',
handler: async (context) => {
return { handoff: 'billing_agent', context };
},
},
],
});
複数のエージェントを使う場面
- 重複しないツールを使う、個別のタスク領域
- 異なる認可レベルが必要な場合
- 明確な引き継ぎポイントがある複雑なワークフロー
- 独立したサブタスクの並列実行
メモリと状態
会話メモリ
// memory/conversation.ts
interface ConversationMemory {
messages: Message[];
maxTokens: number;
add(message: Message): void;
getContext(): Message[];
summarize(): Promise<string>;
}
// Maintain state across tool calls (Gemini pattern)
interface AgentState {
thoughtSignature?: string; // Encrypted reasoning state
conversationId: string; // For shared memory
currentPlan?: Plan;
completedSteps: Step[];
}
永続メモリ
// memory/persistent.ts
interface PersistentMemory {
// Store learnings across sessions
store(key: string, value: any): Promise<void>;
retrieve(key: string): Promise<any>;
// Semantic search over past interactions
search(query: string, limit: number): Promise<Memory[]>;
}
ガードレールと安全性
多層保護(OpenAI)
// guards/index.ts
interface GuardrailConfig {
// Input validation
inputClassifier: (input: string) => Promise<SafetyResult>;
// Output validation
outputValidator: (output: string) => Promise<SafetyResult>;
// Tool risk assessment
toolRiskLevels: Record<string, 'low' | 'medium' | 'high'>;
// Actions requiring human approval
humanInTheLoop: string[];
}
async function executeWithGuardrails(
agent: Agent,
input: string,
config: GuardrailConfig
): Promise<Result> {
// 1. Check input safety
const inputCheck = await config.inputClassifier(input);
if (!inputCheck.safe) {
return { blocked: true, reason: inputCheck.reason };
}
// 2. Execute with tool monitoring
const result = await agent.run(input, {
beforeTool: async (tool, params) => {
const risk = config.toolRiskLevels[tool.name];
if (risk === 'high' || config.humanInTheLoop.includes(tool.name)) {
return await requestHumanApproval(tool, params);
}
return { approved: true };
},
});
// 3. Validate output
const outputCheck = await config.outputValidator(result.output);
if (!outputCheck.safe) {
return { blocked: true, reason: outputCheck.reason };
}
return result;
}
スコープの適用(OpenAI)
// Agent must stay within defined scope
const agentInstructions = `
You are a customer service agent for Acme Corp.
SCOPE BOUNDARIES (non-negotiable):
- Only answer questions about Acme products and services
- Never provide legal, medical, or financial advice
- Never access or modify data outside your authorized scope
- If a request is out of scope, politely decline and explain why
If you cannot complete a task within scope, notify the user
and request explicit approval before proceeding.
`;
モデルの選定
タスクに合ったモデルを選ぶ
| タスクの複雑さ | 推奨モデル | 備考 |
|---|---|---|
| シンプル、高速 | gpt-5-mini, claude-haiku | 低レイテンシ |
| 汎用 | gpt-4.1, claude-sonnet | バランス重視 |
| 複雑な推論 | o4-mini, claude-opus | 高い精度 |
| 深い計画 | gpt-5 + reasoning, ultrathink | 最大限の能力 |
Gemini 固有
// Use thinking_level for reasoning depth
const response = await gemini.generate({
model: 'gemini-3',
thinking_level: 'high', // For complex planning
temperature: 1.0, // Optimized for reasoning engine
});
// Preserve thought state across tool calls
const nextResponse = await gemini.generate({
thoughtSignature: response.thoughtSignature, // Required for function calling
// ... rest of params
});
Claude 固有(思考モード)
// Trigger extended thinking with keywords
const thinkingLevels = {
'think': 'standard analysis',
'think hard': 'deeper reasoning',
'think harder': 'extensive analysis',
'ultrathink': 'maximum reasoning budget',
};
const prompt = `
Think hard about this problem before proposing a solution.
Task: ${task.description}
`;
エージェントのテスト
単体テスト(ツール)
describe('readFile tool', () => {
it('reads file content correctly', async () => {
const result = await readFile({ path: '/test/file.txt' });
expect(result.success).toBe(true);
expect(result.data).toContain('expected content');
});
});
動作テスト(エージェントの判断)
describe('agent planning', () => {
it('creates plan before executing file modifications', async () => {
const trace = await agent.runWithTrace('Refactor the auth module');
// Verify planning happened first
const firstToolCall = trace.toolCalls[0];
expect(firstToolCall.name).toBe('read_file');
// Verify no writes without reads
const writeIndex = trace.toolCalls.findIndex(t => t.name === 'write_file');
const readIndex = trace.toolCalls.findIndex(t => t.name === 'read_file');
expect(readIndex).toBeLessThan(writeIndex);
});
});
評価テスト
// Run nightly, not in regular CI
describe('Agent Accuracy (Eval)', () => {
const testCases = loadTestCases('./evals/coding-tasks.json');
it.each(testCases)('completes $name correctly', async (testCase) => {
const result = await agent.run(testCase.input);
// Verify against expected outcomes
expect(result.filesModified).toEqual(testCase.expectedFiles);
expect(await runTests(testCase.testCommand)).toBe(true);
}, 120000);
});
Pydantic AI のパターン(Python のデフォルト)
プロジェクト構成(Python)
project/
├── src/
│ ├── agents/
│ │ ├── __init__.py
│ │ ├── researcher.py # Research agent
│ │ ├── coder.py # Coding agent
│ │ └── orchestrator.py # Main coordinator
│ ├── tools/
│ │ ├── __init__.py
│ │ ├── web.py # Web search tools
│ │ ├── files.py # File operations
│ │ └── database.py # DB queries
│ ├── models/
│ │ ├── __init__.py
│ │ └── schemas.py # Pydantic models
│ └── deps.py # Dependencies
├── tests/
│ ├── test_agents.py
│ └── test_tools.py
└── pyproject.toml
ツールを備えたエージェント
from pydantic_ai import Agent, RunContext
from pydantic import BaseModel
from httpx import AsyncClient
class SearchResult(BaseModel):
title: str
url: str
snippet: str
class ResearchDeps(BaseModel):
http_client: AsyncClient
api_key: str
research_agent = Agent(
'claude-sonnet-4-6',
deps_type=ResearchDeps,
result_type=list[SearchResult],
system_prompt='You are a research assistant. Use tools to find information.',
)
@research_agent.tool
async def web_search(ctx: RunContext[ResearchDeps], query: str) -> list[dict]:
"""Search the web for information."""
response = await ctx.deps.http_client.get(
'https://api.search.com/search',
params={'q': query},
headers={'Authorization': f'Bearer {ctx.deps.api_key}'},
)
return response.json()['results']
@research_agent.tool
async def read_webpage(ctx: RunContext[ResearchDeps], url: str) -> str:
"""Read and extract content from a webpage."""
response = await ctx.deps.http_client.get(url)
return response.text[:5000] # Truncate for context
# Usage
async def main():
async with AsyncClient() as client:
deps = ResearchDeps(http_client=client, api_key='...')
result = await research_agent.run(
'Find recent articles about LLM agents',
deps=deps,
)
for item in result.data:
print(f"- {item.title}")
検証付きの構造化出力
from pydantic import BaseModel, Field
from pydantic_ai import Agent
class CodeReview(BaseModel):
summary: str = Field(description="Brief summary of the review")
issues: list[str] = Field(description="List of issues found")
suggestions: list[str] = Field(description="Improvement suggestions")
approval: bool = Field(description="Whether code is approved")
confidence: float = Field(ge=0, le=1, description="Confidence score")
review_agent = Agent(
'claude-sonnet-4-6',
result_type=CodeReview,
system_prompt='Review code for quality, security, and best practices.',
)
# Result is validated Pydantic model
result = await review_agent.run(f"Review this code:\n```python\n{code}\n```")
if result.data.approval:
print("Code approved!")
else:
for issue in result.data.issues:
print(f"Issue: {issue}")
複数エージェントの連携
from pydantic_ai import Agent
# Specialized agents
planner = Agent('claude-sonnet-4-6', system_prompt='Create detailed plans.')
executor = Agent('claude-sonnet-4-6', system_prompt='Execute tasks precisely.')
reviewer = Agent('claude-sonnet-4-6', system_prompt='Review and verify work.')
async def orchestrate(task: str):
# 1. Plan
plan = await planner.run(f"Create a plan for: {task}")
# 2. Execute each step
results = []
for step in plan.data.steps:
result = await executor.run(f"Execute: {step}")
results.append(result.data)
# 3. Review
review = await reviewer.run(
f"Review the results:\nTask: {task}\nResults: {results}"
)
return review.data
ストリーミング応答
from pydantic_ai import Agent
agent = Agent('claude-sonnet-4-6')
async def stream_response(prompt: str):
async with agent.run_stream(prompt) as response:
async for chunk in response.stream():
print(chunk, end='', flush=True)
# Get final structured result
result = await response.get_data()
return result
エージェントのテスト
import pytest
from pydantic_ai import Agent
from pydantic_ai.models.test import TestModel
@pytest.fixture
def test_agent():
return Agent(
TestModel(), # Mock model for testing
result_type=str,
)
async def test_agent_response(test_agent):
result = await test_agent.run('Test prompt')
assert result.data is not None
# Test with specific responses
async def test_with_mock_response():
model = TestModel()
model.seed_response('Expected output')
agent = Agent(model)
result = await agent.run('Any prompt')
assert result.data == 'Expected output'
スキルパターン(Anthropic)
スキルの構成
skills/
└── code-review/
├── instructions.md # How to perform code reviews
├── scripts/
│ └── run-linters.sh # Supporting scripts
└── resources/
└── checklist.md # Review checklist
instructions.md の例
# Code Review Skill
## When to Use
Activate this skill when asked to review code, PRs, or diffs.
## Process
1. Read the changed files completely
2. Run linters: `./scripts/run-linters.sh`
3. Check against resources/checklist.md
4. Provide structured feedback
## Output Format
- Summary (1-2 sentences)
- Issues found (severity: critical/major/minor)
- Suggestions for improvement
- Approval recommendation
スキルを動的に読み込む
async function loadSkill(skillName: string): Promise<Skill> {
const skillPath = `./skills/${skillName}`;
const instructions = await fs.readFile(`${skillPath}/instructions.md`, 'utf-8');
const scripts = await glob(`${skillPath}/scripts/*`);
const resources = await glob(`${skillPath}/resources/*`);
return {
name: skillName,
instructions,
scripts: scripts.map(s => ({ name: path.basename(s), path: s })),
resources: await Promise.all(resources.map(loadResource)),
};
}
アンチパターン
- 実行前に計画を立てない - すぐに行動を始めるエージェントは、ミスが増えます
- 単一の巨大なエージェント - 50 個のツールを持つ 1 つのエージェントは、混乱してしまいます
- 検証しない - エージェントは自分の作業を検証する必要があります
- ツールの実行順序を固定する - モデルにツールの実行順序を決めさせましょう
- ガードレールがない - すべてのエージェントに安全上の境界が必要です
- 状態を管理しない - ツール呼び出しの間にコンテキストが失われます
- 成功するケースだけをテストする - 失敗やエッジケースもテストしましょう
- モデルの違いを無視する - 推論モデルには異なるプロンプトが必要です
- コストを追跡しない - エージェント型ワークフローは高コストになる場合があります
- 監督なしで完全に自動化する - 重要な操作には人間が介在する仕組みを導入しましょう
クイックリファレンス
エージェント開発チェックリスト
- エージェントのスコープと境界を明確に定義する
- 明示的なスキーマとリスクレベルを備えたツールを設計する
- 探索、計画、実行、検証のワークフローを実装する
- 多層のガードレールを追加する
- 会話と永続メモリーを設定する
- 振る舞いと評価のテストを作成する
- タスクの複雑さに応じた適切なモデルを設定する
- 高リスクの操作に人間が介在する仕組みを追加する
- トークン使用量とコストを監視する
- スキルと指示を文書化する
思考トリガー(Claude)
"think" → Standard analysis
"think hard" → Deeper reasoning
"think harder" → Extensive analysis
"ultrathink" → Maximum reasoning
Gemini の設定
thinking_level: "high" | "low"
temperature: 1.0 (keep at 1.0 for reasoning)
thoughtSignature: <pass back for function calling>
コメント
Markdown 対応