بازگشت به کتابخانهکتابخانه1.2دیدن کار مدل — stream-json و ralph.js
تکنیک Ralph WiggumTHE RALPH WIGGUM TECHNIQUEاجرای ایجنت کدنویس در حلقه — از یک اسکریپت Bash تا ارکستراسیون کامل
v1.0.0
LESSON 1.2فصل ۱حلقه Ralph — ایده و اولین اجرا

دیدن کار مدل — stream-json و ralph.js

  • ~۸ دقیقه
  • ۴ پرسش
  • متن را انتخاب کن تا هایلایت شود

حلقه درس قبل یک عیب آزاردهنده داشت: چون مدل در Print Mode اجرا می‌شود، تا پایان هر دور هیچ‌چیز نمی‌بینی. مدل دارد کار می‌کند و تو به یک ترمینال ساکت زل زده‌ای. در این درس Observability حلقه را درست می‌کنیم — بدون اینکه معماری Ralph را عوض کنیم.

چرا پلاگین رسمی، Ralph واقعی نیست؟

Anthropic یک پلاگین رسمی دارد که Skill حلقه Ralph را به CLI تعاملی اضافه می‌کند؛ از Marketplace نصب می‌شود و مثلاً می‌گویی «این پرامپت را ۱۰ بار اجرا کن». چون داخل شل تعاملی هستی، کار مدل را زنده می‌بینی و حتی درخواست‌های Permission را دستی تأیید یا رد می‌کنی. ظاهرش عالی است — اما یک تفاوت بنیادی دارد:

راه‌حل: خروجی جریانی JSON

Print Mode یک Flag دارد که پیام‌ها را همان لحظه که تولید می‌شوند بیرون می‌دهد:

claude -p "hello world" --output-format stream-json --verbose

نکته: `stream-json` بدون `--verbose` اجرا نمی‌شود و خطا می‌گیرد. حالا پیام‌ها لحظه‌ای می‌آیند، ولی خامِ JSON‌اند و برای چشم انسان قابل خواندن نیستند. پس باید خودمان Parse کنیم.

اول داده تست بساز، بعد پرامپت بده

قبل از اینکه از ایجنت بخواهیم Parser بنویسد، چند پیام نمونه واقعی تولید می‌کنیم تا در پرامپت به آن ارجاع بدهیم:

claude -p "read a file, do a web fetch, and run a command" \
  --output-format stream-json --verbose > example.txt

حالا `example.txt` نمونه واقعی همه نوع پیام را دارد: شروع Session، متن، Tool Call و نتیجه آن. سپس با یک پرامپت ساده، تبدیل را به خود ایجنت می‌سپاریم:

Look at ralph.sh. Convert it to a Node.js script (ralph.js) that runs
claude with --output-format stream-json --verbose and parses the JSON
messages as they stream in. See example.txt for sample messages.

ralph.js — همان حلقه، با چشم باز

اسکریپت جدید پرحرف‌تر است چون کلی JSON Parsing دارد، ولی قلبش همان For Loop قبلی است. الگوی واقعی از روی `example.txt` ساخته شد:

const { spawn } = require("node:child_process");
const fs = require("node:fs");

const iterations = Number(process.argv[2] ?? 10);
const prompt = fs.readFileSync("prompt.md", "utf8");

function runClaude(args, onMsg) {
  return new Promise((resolve) => {
    const child = spawn("claude", args, { stdio: ["ignore", "pipe", "inherit"] });
    let buf = "";
    child.stdout.on("data", (chunk) => {
      buf += chunk;
      const lines = buf.split("\n");
      buf = lines.pop() ?? "";
      for (const line of lines) {
        if (!line.trim()) continue;
        try { onMsg(JSON.parse(line)); } catch { /* keep buffering */ }
      }
    });
    child.on("close", () => resolve());
  });
}

(async () => {
  for (let i = 1; i <= iterations; i++) {
    console.log(`=== Iteration ${i} of ${iterations} ===`);
    let done = false;
    await runClaude(
      ["-p", prompt, "--output-format", "stream-json", "--verbose"],
      (msg) => {
        if (msg.type === "assistant") process.stdout.write(msg.message ?? "");
        if (msg.type === "tool_use") console.log(`→ ${msg.name}`, msg.input);
        if (msg.type === "result" && String(msg.result ?? "").includes("ALL TASKS COMPLETE")) {
          done = true;
        }
      },
    );
    if (done) {
      console.log(`All tasks completed after ${i} iterations.`);
      break;
    }
  }
})();

معماری Ralph تغییری نکرد: هنوز Print Mode، هنوز Context تازه در هر دور، هنوز Magic String. فقط حالا با `node ralph.js` می‌بینی مدل همین الان دارد چه فایلی می‌خواند و چه دستوری می‌زند — و حلقه «زنده» به نظر می‌رسد.

DIAGRAMمسیر خروجی جریانی
claude -p
stream-json + verbose
ralph.js parser
خروجی خوانا در ترمینال

به زبان ساده

Print Mode تا پایان دور ساکت است. با stream-json پیام‌ها زنده می‌آیند؛ یک اسکریپت Node همان حلقه را نگه می‌دارد و JSON را برای چشم انسان ترجمه می‌کند.

مثال واقعی

اول `example.txt` را با یک اجرای واقعی ساختند (خواندن فایل، Web Fetch، یک دستور)، بعد همان فایل را به مدل دادند تا ralph.js را بنویسد — حدس زدن فرمت JSON لازم نشد.

دانش‌سنجی

آزمون درس

۴ Q
01
تفاوت بنیادی پلاگین رسمی Ralph با اسکریپت خودمان چیست؟
02
برای دیدن لحظه‌ای پیام‌ها در Print Mode چه می‌کنیم؟
03
چرا قبل از نوشتن پرامپتِ ساخت ralph.js، فایل example.txt ساختیم؟
04
بعد از تبدیل ralph.sh به ralph.js، معماری Ralph چه تغییری کرد؟