پیش از هر کار، پرسیدن
در پایانِ بخشِ قبل، mini میتواند فایل بخواند، فایل بنویسد و هر دستورِ شِلی را در پوشهاش اجرا کند. هر چه مدل بخواهد، همان لحظه انجام میدهد. برای آزمایش روی پوشهای پر از فایلهای بیارزش این اشکالی نداشت. اما لیلا میخواهد mini را سراغِ گزارشهای واقعیاش بفرستد، و پیش از آن mini باید کاری را بکند که همهی عاملهایی که لیلا با آنها کار کرده میکنند: بایستد و بپرسد.
لیلا سادهترین راه این است که در هر ابزار یک بررسی بگذارم. write_file بپرسد «مطمئنی؟»، run_command هم بپرسد.
امید برای چهار ابزار جواب میدهد. وقتی ابزارِ پنجم را اضافه کنیم چه؟ یا ابزارِ ششمی که کسِ دیگری مینویسد؟
لیلا هر کدام بررسیِ خودش را لازم دارد، و دیر یا زود یکی یادش میرود.
امید پس یک بررسی را کجا میشود گذاشت که هیچ فراخوانیِ ابزاری نتواند دورش بزند؟
لیلا در run_tool. هر فراخوانیای که از مدل میآید، پیش از رسیدن به هر ابزاری از آن میگذرد.
امید درست است. Claude Code هم اجازههایش را همانجا بررسی میکند: تنها دری که بینِ خواستهی مدل و کامپیوترِ تو هست. حالا وقتی بررسی رد میکند، چه باید بگوید؟
لیلا هیچ؟ فقط ابزار را اجرا نمیکند.
امید پس به درخواستِ بعدی فکر کن. مدل یک ابزار خواسته و جوابی نگرفته. چه میبیند؟
لیلا یک فراخوانیِ ابزار که هیچ چیز بعدش نیامده. یا گیر میکند، یا دوباره همان کار را امتحان میکند.
امید تازه API اصلاً چنین درخواستی را نمیپذیرد: هر بلوکِ tool_use باید با یک tool_result با همان شناسه جواب بگیرد. پس رد کردن هم یک نتیجه است. فقط میگوید «جلویش گرفته شد» و چرا.
یک تابع تصمیم میگیرد
بررسیِ اجازه (permission check) تابعی است که هارنس پیش از اجرای هر فراخوانیِ ابزار صدا میزند تا بگوید آن فراخوانی اجازهی اجرا دارد یا نه. نامش در mini check_permission است. نامِ ابزار و ورودیهایش را میگیرد و اگر فراخوانی میتواند اجرا شود None برمیگرداند، وگرنه دلیلی کوتاه:
ALLOW = {"read_file", "list_files", "load_skill", "delegate"} # never ask
DENY_COMMANDS = ["rm -rf", "git push", "curl ", "sudo "]
def check_permission(name, args):
"""None if the call may run, else the reason it may not."""
if "path" in args and inside(args["path"]) is None:
return f"{args['path']} is outside the project folder"
if name == "run_command" and any(bad in args["command"] for bad in DENY_COMMANDS):
return "that command is not allowed in mini"
if name in ALLOW:
return None
answer = input(f"\nmini wants to {name} {json.dumps(args)[:200]}. Allow? [y/N] ")
return None if answer.strip().lower() == "y" else "the user said no"آن را از بالا بخوانید، چون ترتیبِ خطها همان طراحی است:
- آنچه هرگز نباید رخ دهد رد میشود. مسیری بیرون از پوشهی پروژه (
insideهمان تابعِ درسِ «ابزارهایی که به فایلها دست میزنند» است) و دستورهای فهرستِ ممنوع، پیش از هر چیزِ دیگری رد میشوند. هیچ قاعدهی اجازهای و هیچ «بله»ای از آدمی خسته نمیتواند از این خطها بگذرد. - آنچه همیشه بیخطر است اجازه میگیرد. خواندن و فهرست کردنِ فایلها چیزی را تغییر نمیدهد، پس هیچوقت نمیپرسد. (
load_skillوdelegateدر یکی از بخشهای بعد میآیند؛ آنها هم فقط میخوانند.) - دربارهی بقیه پرسیده میشود. نوشتنِ فایل یا اجرای دستور منتظرِ آدم میماند. هر جوابی جز
yیعنی نه، پس اگر به اشتباه Enter بزنید اتفاقی نمیافتد.
این همان شکلی است که در اجازههای عامل دیدید: اول ممنوع، بعد مجاز، بعد پرسیدن. اینجا همان را در دوازده خطِ خوانا دارید.
وصل کردنِ آن به run_tool
بررسی درست پیش از اجرای ابزار، در run_tool قرار میگیرد:
def run_tool(block, allowed):
name, args = block.name, block.input
reason = None if name in allowed else f"no tool called {name}"
reason = reason or check_permission(name, args)
if reason:
return {"type": "tool_result", "tool_use_id": block.id,
"content": f"Blocked: {reason}", "is_error": True}
... # run the tool, as beforeبه سه نکته دقت کنید:
- رد کردن هم یک
tool_resultاست، با همانtool_use_idفراخوانیای که به آن جواب میدهد. گفتوگو معتبر میماند و مدل میفهمد چه شد. is_errorبرابرTrueاست. این به مدل میگوید این فراخوانی کار نکرد، درست مثل ابزاری که خطا داده، تا راهِ دیگری انتخاب کند و طوری ادامه ندهد که انگار فایل نوشته شده است.- دلیل برای مدل نوشته شده است. «the user said no» باعث میشود مدل بایستد و بپرسد لیلا چه میخواهد؛ «outside the project folder» باعث میشود مسیری درونِ پوشه را امتحان کند. یک «نه»ی خالی مدل را سرگردان میگذارد.
کاری که فهرستِ ممنوع نمیتواند بکند
فهرستِ ممنوع خیال را راحت میکند، اما فقط متن را مقایسه میکند. rm -rf old/ رد میشود، اما rm -r -f old/ نه، و find old -delete هم نه، و نه یک خط پایتون که همان فایلها را پاک کند. لازم نیست مدل نیتِ بدی داشته باشد تا یکی از اینها را بنویسد: فقط میخواهد کار را تمام کند، و وقتی راهِ اول بسته است راهِ دیگری برمیدارد.
پس هر قاعده را برای کاری به کار ببرید که در آن خوب است:
- فهرستِ مجاز محکم است. «خواندن هیچوقت نمیپرسد، بقیه همه میپرسند» را نمیشود دور زد، چون هر چه در آن نامی ندارد به پرسش میرسد.
- فهرستِ ممنوع یک سرعتگیر است. اشتباهِ رایج را میگیرد و دلیلی روشن به مدل میدهد. جلوی کسی را که راهِ دیگری پیدا کند نمیگیرد.
- مرزهای واقعی از بیرونِ برنامه میآیند. برای اینکه مطمئن شوید یک دستور به چیزِ دیگری دست نمیزند، mini را درونِ یک سندباکس (sandbox) اجرا کنید: یک کانتینر یا یک حسابِ کاربریِ جدا که فقط به یک پوشه دسترسی دارد و به شبکه نه. آن وقت حتی دستوری که از همهی قاعدهها بگذرد هم فقط به همان چیزی میرسد که آنجاست.
امتحان کنید
کلیدِ API را تنظیم کنید، mini را در یک پوشهی تمرینی اجرا کنید و از آن بخواهید «create a file called hello.txt saying hi». وقتی پرسید،
nبزنید. بعد بخوانید که پس از آن چه میگوید: ردِ شما را بهصورتِ نتیجهی ابزار دیده است. دوباره اجرایش کنید و این بارyبزنید. کلید ندارید؟ دو پیامِ تصویرِ دوم را بخوانید و با کلماتِ خودتان بنویسید که مدل بعد از هر کدام چه میداند.
درست به کار بردن
- بررسی را در یک جا بگذارید. همهی فراخوانیها باید پیش از اجرای هر ابزاری از یک تابع بگذرند، تا هیچ ابزارِ تازهای بیبررسی وارد نشود.
- ردهای قطعی را اول بگذارید. قاعدههایی که هرگز نباید نادیده گرفته شوند، بالای فهرستِ مجاز و پرسش میآیند تا هیچ چیزِ بعدی نتواند آنها را بیاثر کند.
- به هر فراخوانیِ ابزار جواب بدهید. فراخوانیِ ردشده یک
tool_resultبا شناسهی خودش وis_errorمیگیرد، وگرنه درخواستِ بعدی شکست میخورد و مدل سرگردان میماند. - به فهرستِ مجاز بیشتر از فهرستِ ممنوع اعتماد کنید. آنچه را بیخطر است فهرست کنید و دربارهی بقیه بپرسید؛ برای آنچه باید ناممکن باشد، سندباکس به کار ببرید.
واژههای این درس
- بررسیِ اجازه (permission check) - تابعی که هارنس پیش از هر فراخوانیِ ابزار صدا میزند و فراخوانی را اجرا میکند، از آدم میپرسد، یا با یک دلیل ردش میکند
- فهرستِ ممنوع (deny list) - الگوهایی که همیشه رد میشوند؛ نوشتنش آسان است، اما با دستوری که جورِ دیگری نوشته شده بهآسانی دور زده میشود
is_error- بخشی ازtool_resultکه به مدل میگوید فراخوانیاش شکست خورده یا جلویش گرفته شده، پس باید راهِ دیگری امتحان کند
ایدهی اصلی
در عاملِ خودتان، اجازهها یک تابعاند که هر فراخوانیِ ابزار پیش از اجرا از آن میگذرد: آنچه هرگز نباید رخ دهد رد کنید، آنچه همیشه بیخطر است اجازه دهید، و دربارهی بقیه بپرسید. رد کردن هم یک جواب است که بهصورتِ نتیجهی خطا برمیگردد، تا مدل بداند و راهش را عوض کند.