AIエージェントを「使う」のではなく「作れる」エンジニアになるには、フレームワークのブラックボックスを開ける必要がある。pguso/ai-agents-from-scratchはローカルLLMだけを使い、Function Calling・永続メモリ・ReActパターンをゼロから実装する14ステップのJavaScript学習リポジトリだ。GitHubスター3,592・フォーク541を集め、「フレームワークを使う前に原理を理解したい」という開発者の需要を直撃している。
AIエージェントのアーキテクチャ比較や本番運用設計については、AIエージェントフレームワーク徹底比較2026:LangGraph・CrewAI・AutoGenの選び方 で詳しく解説しています。
ai-agents-from-scratchとは何か
作者: pguso(Philip Guso)
スター: 3,592 / フォーク: 541
言語: JavaScript(Node.js 18+)
使用ライブラリ: node-llama-cpp
ライセンス: MIT
特徴: ローカルLLM・フレームワーク不使用・14ステップ段階学習
本リポジトリは「AIエージェントを自分で作ることで、フレームワークが内部で行っている設計判断を理解する」という明確な哲学のもとに設計されている。公式説明文には「Demystify AI agents by building them yourself. Local LLMs, no black boxes, real understanding of function calling, memory, and ReAct patterns.(AIエージェントを自ら構築することで神秘を解く。ローカルLLM・ブラックボックスなし・関数呼び出し・メモリ・ReActパターンの真の理解)」とある。
なぜ今、ゼロから学ぶのか
LangChain・LlamaIndex・CrewAIといったフレームワークは開発速度を大幅に向上させるが、「なぜそう動くのか」を知らないまま使うと本番環境でのデバッグが難しくなる。
- エラーの原因特定が困難:フレームワークの内部ループで発生するエラーはスタックトレースが深くなり、根本原因が見えにくい
- 設計判断の自由度が下がる:抽象化されたAPIに縛られ、特定のユースケースに最適化できない
- 最新パターンへの追従が遅れる:フレームワークのアップデート待ちになる
本リポジトリはこれらの問題を「フレームワークを使わずに原理を実装する」という方法で解決する教育アプローチを取っている。
コンパニオンサイトとの併用
2026年初頭にはコンパニオンウェブサイト agentsfromscratch.com も開設された。サイトはコードの実行場所ではなく「学習マップ」として機能し、各ステップの「なぜそのパターンが存在するのか」を可視化している。READMEは「GitHubをテレイン(地形)、サイトをマップ(地図)」と表現しており、両者の役割が明確に分かれている。
14ステップの学習パス
リポジトリは examples/ 以下に14個のフォルダを用意し、順番に進めることでLLMの基礎からマルチステップ推論まで段階的に習得できる構成になっている。
intro
基本LLM呼び出し"] --> B["02
openai-intro
クラウドAPI"] B --> C["03
translation
システムプロンプト"] C --> D["04
think
推論"] D --> E["05
batch
並列処理"] E --> F["06
coding
ストリーミング"] F --> G["07
simple-agent
Function Calling"] G --> H["08
memory-agent
永続メモリ"] H --> I["09
react-agent
ReAct"] I --> J["10
aot-agent
AoT"] J --> K["11
error-handling
エラー耐性"] K --> L["12
tree-of-thought
ToT"] L --> M["13
graph-of-thought
GoT"] M --> N["14
chain-of-thought
CoT"] style G fill:#ff9999,color:#000 style I fill:#ff9999,color:#000 style L fill:#ffdd99,color:#000 style M fill:#ffdd99,color:#000 style N fill:#ffdd99,color:#000
ステップ1〜6:LLM基礎編
最初の6ステップはAIエージェントになる前の基礎スキルを積み上げる。
ステップ1(intro): ローカルLLMのロード・コンテキスト作成・推論実行の最小実装。node-llama-cppがどのようにGGUFモデルファイルを読み込み、コンテキストを管理するかを理解する。
ステップ2(openai-intro): ローカルLLMとクラウドAPI(GPT-4等)の呼び出し方を比較する。温度パラメータ・トークン使用量・レイテンシ・コスト・データプライバシーの違いを実感する。
ステップ3(translation): システムプロンプトを使ってLLMに役割を与える。翻訳エージェントを例に「エージェントの個性はシステムプロンプトで決まる」という原則を体験する。
ステップ4(think): 論理推論タスクと純粋なLLM推論の限界を探る。複雑な定量問題でLLMが計算を誤るパターンを確認し、「外部ツールが必要になる理由」を理解する。
ステップ5(batch): contextSequencesを使った並列リクエスト処理。GPUバッチ処理の仕組みとスループット最適化を学ぶ。
ステップ6(coding): ストリーミングレスポンスとトークンバジェット管理。リアルタイムフィードバックのUX実装とレスポンス制御の手法を習得する。
ステップ7:Function Calling — テキスト生成からエージェンシーへ
defineChatSessionFunction を使ってLLMに渡すツールを定義し、LLMが「いつツールを使うか」を自律的に判断する仕組みを実装する。
import {defineChatSessionFunction, getLlama, LlamaChatSession} from "node-llama-cpp";
const llama = await getLlama({debug: false});
const model = await llama.loadModel({
modelPath: path.join(__dirname, '../../models/Qwen3-1.7B-Q8_0.gguf')
});
const context = await model.createContext({contextSize: 2000});
const systemPrompt = `You are a professional chronologist who standardizes time representations.
Always convert times from 12-hour format (e.g., "1:46:36 PM") to 24-hour format (e.g., "13:46")
before returning them.`;
const session = new LlamaChatSession({
contextSequence: context.getSequence(),
systemPrompt,
});
// ツール定義:LLMがいつ呼ぶかを自律判断する
const getCurrentTime = defineChatSessionFunction({
description: "Get the current time",
params: {
type: "object",
properties: {}
},
async handler() {
return new Date().toLocaleTimeString();
}
});
const functions = {getCurrentTime};
const result = await session.prompt("What time is it right now?", {functions});
console.log("AI:", result);
このコードでLLMは「時刻を聞かれた → getCurrentTimeを呼ぶ必要がある → JSON形式でツール呼び出しを発行 → 結果を受け取ってフォーマット変換 → ユーザーに返答」という一連の判断を自律的に行う。
ステップ8:永続メモリ
セッションをまたいで情報を記憶するシステムを実装する。MemoryManagerクラスがJSONファイルへの読み書きを担当し、システムプロンプトに既存の記憶を注入する設計になっている。
import {MemoryManager} from "./memory-manager.js";
// 既存の記憶を読み込んでシステムプロンプトに注入
const memoryManager = new MemoryManager('./agent-memory.json');
const memorySummary = await memoryManager.getMemorySummary();
const systemPrompt = `
You are a helpful assistant with long-term memory.
Before calling any function, always follow this reasoning process:
1. Compare new user statements against existing memories below.
2. If the same key and value already exist, do NOT call saveMemory again.
3. If the user provides an updated value, then call saveMemory once to update.
4. Only call saveMemory for genuinely new information.
${memorySummary}
`;
// 記憶保存ツール
const saveMemory = defineChatSessionFunction({
description: "Save important information to long-term memory",
params: {
type: "object",
properties: {
type: { type: "string", enum: ["fact", "preference"] },
key: { type: "string" },
value: { type: "string" }
},
required: ["type", "key", "value"]
},
async handler({type, key, value}) {
await memoryManager.addMemory({type, key, value});
return `Memory saved: ${key} = ${value}`;
}
});
重要なのは「重複を避けるロジックをシステムプロンプトで指示している」点だ。同じキーと値がすでに存在する場合はsaveMemoryを呼ばないよう明示的に指示することで、不要な書き込みを防いでいる。
ステップ9:ReActエージェント — 現代エージェントの基盤
READMEに「This is the foundation of modern agent frameworks!(これが現代エージェントフレームワークの基盤だ!)」と記述されているほど重要なステップ。LangGraph・CrewAI・AutoGenはこのパターンを内部で実装している。
ReActはシステムプロンプトで厳格なフォーマットを強制することで実現される:
const systemPrompt = `You are a mathematical assistant that uses the ReAct approach.
CRITICAL: You must follow this EXACT pattern for every problem:
Thought: [Explain what calculation you need to do next and why]
Action: [Call ONE tool with specific numbers]
Observation: [Wait for the tool result]
Thought: [Analyze the result and decide next step]
Action: [Call another tool if needed]
Observation: [Wait for the tool result]
... (repeat as many times as needed)
Thought: [Once you have ALL information needed]
Answer: [Give the final answer and STOP]
RULES:
1. Only write "Answer:" when you have the complete final answer
2. After writing "Answer:", DO NOT continue calculating
3. Break complex problems into the smallest possible steps
4. Use tools for ALL calculations - never calculate in your head
5. Each Action should call exactly ONE tool`;
Thought → Action → Observationのループを出力形式として強制することで、LLMは複数ステップの問題を段階的に解けるようになる。ツール実行結果(Observation)を次のThoughtに組み込む設計が「観察→思考→行動」の自律サイクルを実現する。
高度な推論パターン:ステップ10〜14
ステップ10以降では、現代のエージェント研究で使われている高度な推論パターンを実装する。
Atom of Thought(AoT)— ステップ10
複雑なタスクを「アトム(原子的操作)」に分解し、依存関係を解決しながら確定的に実行するパターン。構造化JSONで推論プランを出力し、プランに従って実行するアーキテクチャになっている。
- 原子的操作: 各ステップを最小単位に分割
- 依存関係管理: 先行ステップの完了を待ってから後続ステップを実行
- 確定的実行: LLMの出力ではなくコードがプランを実行
エラーハンドリング — ステップ11
LLM + ツールのシステムに特有のエラーパターンを体系化し、回復戦略を実装する。
- エラー分類: 検証エラー・LLMエラー・ツールエラー・ワークフローエラーの4タイプ
- リトライポリシー: バックオフ・ジッタ付きリトライと一時的失敗の分類
- グレースフルデグラデーション: LLMパスが失敗した場合の確定的フォールバック
- 相関ID: 本番サポートのためのエラー追跡
Tree of Thought(ToT)・Graph of Thought(GoT)・Chain of Thought(CoT)— ステップ12〜14
3つの推論パターンは「いつ何を使うか」の設計判断が重要だ。
| パターン | 動作原理 | 適するタスク | 計算コスト |
|---|---|---|---|
| Chain of Thought | 単一の線形推論チェーン | 監査可能な意思決定・段階的説明 | 低 |
| Tree of Thought | 複数の候補パスを生成→評価→枝刈り | 競合する選択肢から最良を選ぶ探索問題 | 中〜高 |
| Graph of Thought | 複数ソースをDAGで並列処理→マージ | 複数情報源を統合した一貫性のある回答生成 | 中 |
| ReAct | Thought→Action→Observationの反復 | ツールを使う実世界の多段タスク | 中 |
| AoT | 原子操作に分解→依存解決→確定実行 | 計算の依存関係が明確な構造化タスク | 低〜中 |
使い分け判断:
- ToTは「複数の戦略から最良を探す」ときに使う(ビームサーチ的アプローチ)
- GoTは「複数の情報源を一つの一貫したポリシーに統合する」ときに使う
- CoTは「なぜその判断をしたか監査可能にしたい」ときに使う
- ReActはツールを使う実行型エージェントの標準パターン
アーキテクチャパターンの進化
14ステップを通じて、エージェントのアーキテクチャがどのように進化するかをコードで確認できる。READMEが示す進化図は単純なLLM呼び出しから始まり、段階的に能力を追加していく様子を表している。
シンプルエージェント(ステップ1〜6)
User → LLM → Response
最小構成。LLMはユーザーの入力を受け取り、一度の推論でレスポンスを返す。ツールなし・メモリなし・反復なし。
ツール使用エージェント(ステップ7)
User → LLM ⟷ Tools → Response
LLMがツールを呼び出し、その結果を使ってレスポンスを生成する。「テキスト生成」から「外部世界への作用」へのジャンプが起きる。
メモリエージェント(ステップ8)
User → LLM ⟷ Tools → Response
↕
Memory
セッションをまたいで状態を保持する。ユーザーの名前・好み・過去の操作を次の会話に引き継げる。
ReActエージェント(ステップ9)
User → LLM → Think → Act → Observe
↑ ↓ ↓ ↓
└──────┴──────┴──────┘
Iterate until solved
解が得られるまでループする。複数ステップの問題・複数ツールの組み合わせ・自己修正が可能になる。
セットアップと実行方法
環境要件
- Node.js 18以上
- RAM 8GB以上(16GB推奨)
- GGUFモデルファイル(Hugging Faceからダウンロード)
インストール手順
# リポジトリのクローン
git clone https://github.com/pguso/ai-agents-from-scratch.git
cd ai-agents-from-scratch
# 依存関係のインストール(node-llama-cpp等)
npm install
# モデルをダウンロードしてmodels/に配置
# DOWNLOAD.mdの手順に従う
# 推奨: Qwen3-1.7B-Q8_0.gguf(軽量で動作確認済み)
# 各ステップの実行例
node examples/01_intro/intro.js
node examples/07_simple-agent/simple-agent.js
node examples/09_react-agent/react-agent.js
プロジェクト構造
リポジトリは examples/ 以下に14個のフォルダを持ち、各フォルダが以下の3ファイルで構成されている:
<name>.js: 動作するコード例CODE.md: コードのライン別解説(何をしているか・どう動くか)CONCEPT.md: 高レベルの概念説明(なぜこのパターンが重要か・実世界での応用)
この構造は「コードを読む → 概念を理解する」の往復を強制する設計で、コピペして終わりではなく「理解してから次へ」という学習体験を作っている。
デバッグツール:PromptDebugger
helper/prompt-debugger.js はLLMに送信されるプロンプトの内容を可視化するユーティリティだ。
const promptDebugger = new PromptDebugger({
outputDir: './logs',
filename: 'debug_prompts.txt',
includeTimestamp: true,
appendMode: false
});
// セッション終了後にプロンプト状態を出力
await promptDebugger.debugContextState({session, model});
システムプロンプト・関数定義・会話履歴・コンテキスト状態を出力するため、「LLMが実際に何を見ているか」を確認できる。本番デバッグで特に有用なツールだ。
node-llama-cppについて
本リポジトリのランタイムである node-llama-cpp はllama.cppのNode.jsバインディングで、GGUFフォーマットの量子化モデルをCPU/GPUで実行できる。
- GGUFフォーマットのモデルをNode.jsから直接実行
- CPU・GPU(Metal/CUDA/ROCm)に対応
- チャットセッション管理・関数呼び出し・ストリーミングをネイティブサポート
defineChatSessionFunctionでJSON Schema準拠のツール定義が可能- TypeScript型定義付き
モデルはHugging FaceのGGUFライブラリから選択する。本リポジトリのコード例では主に以下を使用している:
- Qwen3-1.7B-Q8_0.gguf: 軽量・高速。Function CallingとMemoryの例で使用
- hf_giladgd_gpt-oss-20b.MXFP4.gguf: より高精度の推論。ReAct例で使用
量子化(Q8_0・MXFP4等)によりモデルサイズを圧縮しながら精度を保持するGGUFフォーマットは、ローカル実行の実用性を大きく向上させている。ローカルLLMの選択と実行環境については ローカルLLMとは?2026年版ツール比較——Ollama・Lemonade・LM Studio・GPT4Allの選び方 も参考になる。
既存のエージェント学習教材との比較
| 教材 | 言語 | アプローチ | ローカルLLM | 推論パターン数 | 対象者 |
|---|---|---|---|---|---|
| ai-agents-from-scratch | JavaScript | フレームワーク不使用・原理実装 | 必須 | 6(ReAct/AoT/ToT/GoT/CoT) | 理解重視の開発者 |
| rohitg00/ai-engineering-from-scratch | Python/TS | 283レッスン・20フェーズ | 選択 | 広範 | 体系的学習者 |
| 12-factor-agents | TypeScript | 設計原則(12原則) | 非依存 | 原則集 | 本番開発者 |
| LangChain公式チュートリアル | Python | フレームワーク使用 | 選択 | 4〜5 | 速度重視の開発者 |
| LlamaIndex Starter | Python | フレームワーク使用 | 選択 | 3〜4 | RAG統合開発者 |
AI Engineering from Scratch:283レッスン20フェーズで学ぶAIエンジニア養成カリキュラムはより広い範囲(線形代数からスウォームまで)をカバーするが、ai-agents-from-scratchは「エージェントだけ」に絞り込んでコードを深く読む体験を提供している。
学習上の注意点
モデル選択について
node-llama-cppはモデルのパスをハードコードする形式のため、新しいモデルに切り替えると各ファイルのパスを変更する必要がある。GitHubのissueでも指摘されているため、実際に試す場合は DOWNLOAD.md を参照してモデルを配置後、自分の環境に合わせてパスを調整する必要がある。
Function Callingの精度
Function Callingの精度はモデルに強く依存する。Qwen3-1.7Bは軽量だが、複雑な多段推論では誤ったツール呼び出しを行う場合がある。ReActのステップ(09)ではより大きなモデル(20Bクラス)を推奨している理由はここにある。
コンテキストサイズの制限
ステップ8(永続メモリ)のシステムプロンプトは記憶が増えるほど長くなる。contextSize: 2000 という設定は多くの会話で不足する可能性があり、長期運用では適切なコンテキストサイズ管理とメモリ圧縮戦略が必要になる。
本番エージェント設計への橋渡し
本リポジトリで学んだ原理は、本番エージェント設計に直結する。12-Factor Agents完全解説:本番投入できるLLMエージェント設計12原則が提唱する設計原則と対応させると以下のようになる:
- ステップ7(Function Calling) → Factor 1「自然言語をツール呼び出しに変換」
- ステップ8(メモリ) → Factor 5「実行状態とビジネス状態の統一」
- ステップ9(ReAct) → Factor 8「制御フローを自分で所有する」
- ステップ11(エラーハンドリング) → Factor 10「小さなモデル・シンプルな制御フロー」
本リポジトリを終えた後に12-Factor Agentsを読むと、「なぜそのような設計原則が生まれたか」がゼロから実装した経験として腑に落ちる。
まとめ:フレームワークを賢く使うための「前段」
pguso/ai-agents-from-scratchは「フレームワーク不要論」ではなく「フレームワークを賢く使うための前段」として設計されている。14ステップを通じて:
- Function Calling: LLMがツールを呼ぶ仕組みをJSON Schema定義から理解する
- 永続メモリ: ステートレスなLLMをステートフルなエージェントに変える実装を書く
- ReAct: Thought→Action→Observeループを自分で制御することで、LangGraphの内部を想像できるようになる
- 高度推論(ToT/GoT/CoT): 問題に応じた推論パターンの選択基準を体験的に学ぶ
ローカルLLMで動くため、APIコストゼロで何度でも試行錯誤できる。Node.js 18と8GB RAMがあれば今日から始められるという実用的なハードルの低さも、3,592スターの背景にある。