nabuguides
Englishبازگشت به همهٔ راهنماها ←
BUILD · LANGGRAPH · HUMAN REVIEW · SPECULOS

بیا اولین ایجنت واقعی‌ات رو از صفر بسازیم

این دفعه دیگه فقط دربارهٔ ایجنت حرف نمی‌زنیم؛ با هم یکی می‌سازیم. ایجنت پیشنهاد می‌ده، قانون‌ها چکش می‌کنن و تو تأیید یا رد می‌کنی. تا اجازه ندی، هیچ کاری جلو نمی‌ره.

نسخهٔ خیلی سادهایجنت مثل یک دستیار باهوشه که می‌تونه پیشنهاد بده و مراحل کار رو جلو ببره. ولی کلید، بودجه و اجازهٔ نهایی رو بهش نمی‌دیم. این پروژه دقیقاً همین جدایی رو عملی می‌کنه.
پیش‌نویس فارسی · بررسی‌شده با مستندات رسمی · ۳۰ آگوست ۲۰۲۶

تازه رسیدی؟ اول مسیر قبلی رو در دو دقیقه مرور کن

لازم نیست همهٔ مقاله‌ها رو همین الان بخونی. هر کارت یک یادآوری یک‌جمله‌ای داره؛ اگر مفهومش برات جدیده، کلیک کن. اگر قبلاً خوندی، از روش رد شو.

این مقاله چه چیز تازه‌ای اضافه می‌کنه؟

قبلی‌ها قطعات لگو رو معرفی کردن. اینجا برای اولین بار همشون رو روی میز می‌چینیم و یک ماشین کوچیکِ قابل‌اجرا می‌سازیم.

آخر کار دقیقاً چی داری؟

01یک agent واقعی

درخواست رو می‌خونه و یک عملیات ساختاریافته پیشنهاد می‌ده.

02قانونی که دور زده نمی‌شه

مبلغ یا مقصد خارج از policy قبل از تأیید آدم بلاک می‌شه.

03توقف و ادامه

LangGraph state رو نگه می‌داره تا تو approve یا reject کنی.

04signer جدا

agent به seed یا private key دسترسی پیدا نمی‌کنه.

05Speculos آموزشی

بدون دستگاه، رابط یک Ledger App رو روی کامپیوتر می‌بینی.

06تست مسیر شکست

مطمئن می‌شی درخواست خطرناک واقعاً متوقف می‌شه.

نقشهٔ پروژه قبل از کدنویسی

USERدرخواست می‌ده
AGENTفقط پیشنهاد می‌ده
POLICYمبلغ و مقصد
HUMANapprove / reject
AUDIT LOGپیشنهاد، تصمیم و نتیجه را ثبت می‌کند
SIGNER BOUNDARYdemo · Speculos · hardware
RESULTexecute / reject
مرز اعتماد اینجاست: agent می‌تونه دادهٔ عملیات رو بسازه؛ ولی signer و policy بیرون از runtime ایجنت می‌مونن.
یک اصلاح مهم

تأیید آدم نباید اجازه بده policy سخت دور زده بشه. اگر سقف مبلغ ۰٫۰۱ است، حتی آدم هم از داخل همین flow نمی‌تونه درخواست ۱ واحدی را «همین‌طوری» رد کند و جلو ببرد؛ باید policy جداگانه تغییر کند.

قبل از شروع چی نصب کنیم؟

Python 3.11 یا جدیدتر

کد پروژه با Python اجرا می‌شه. بعد از نصب، در ترمینال python3 --version رو بزن.

Ollama برای مدل رایگان روی کامپیوتر

مسیر اصلی مقاله رایگان و local است. اگر سیستم ضعیفه، می‌تونی بعداً adapter مدل رو با یک API ابری عوض کنی.

یک ادیتور ساده

VS Code خوبه، ولی هر ادیتوری که فایل متنی ذخیره کنه کافیه.

Docker فقط برای بخش Speculos

برای ساخت خود agent لازم نیست. وقتی رسیدیم به emulator، نصبش می‌کنیم.

سخت‌افزار ضعیفه؟

اول پروژه رو با node ساختگیِ پیشنهاد اجرا کن یا از API ابری استفاده کن. بخش policy، human review و signer boundary به مدل بزرگ احتیاج ندارن؛ مهم‌ترین یادگیری مقاله همین قسمت‌هاست.

قدم یک: پوشه و محیط پروژه

TERMINAL
mkdir first-guarded-agent
cd first-guarded-agent
python3 -m venv .venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install langgraph langchain langchain-ollama pydantic python-dotenv

بعد یک مدل کوچک در Ollama آماده کن. اسم مدل رو می‌تونی براساس سخت‌افزارت عوض کنی:

OLLAMA
ollama run qwen3:4b
اگر این مدل برای سیستمت سنگینه، از راهنمای مدل آفلاین کمک بگیر یا مدل کوچک‌تری انتخاب کن.

قدم دو: مدل فقط یک Proposal می‌سازه

اولین قانون معماری: مدل هیچ تابعی برای «ارسال» یا «امضا» نمی‌بینه. خروجی‌اش فقط یک فرم پیشنهاده.

agent.py · proposal
import os
from typing import TypedDict
from pydantic import BaseModel, Field
from langchain_ollama import ChatOllama
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import interrupt, Command

class TransferProposal(BaseModel):
    asset: str = Field(description="demo asset symbol")
    amount: float = Field(gt=0)
    destination: str
    reason: str

class AgentState(TypedDict, total=False):
    request: str
    proposal: dict
    policy: dict
    approved: bool
    result: str

model = ChatOllama(
    model=os.getenv("OLLAMA_MODEL", "qwen3:4b"),
    temperature=0,
)
proposal_model = model.with_structured_output(TransferProposal)

def propose(state: AgentState):
    proposal = proposal_model.invoke(
        "Create a DEMO transfer proposal only. "
        "Never claim that anything was executed. Request: " + state["request"]
    )
    return {"proposal": proposal.model_dump()}
Structured output یعنی چی؟

به‌جای اینکه مدل یک پاراگراف شلخته بده، مجبورش می‌کنیم چهار خانهٔ مشخص رو پر کنه: دارایی، مبلغ، مقصد و دلیل.

قدم سه: policy قبل از آدم تصمیم می‌گیره

تو این تمرین فقط دو مقصد آزمایشی مجازن و سقف مبلغ هم ۰٫۰۱ است. اگر درخواست با یکی از این قانون‌ها جور درنیاد، مسیر همون‌جا تموم می‌شه.

agent.py · policy
MAX_AMOUNT = 0.01
ALLOWLIST = {"demo-alice", "demo-bob"}

def policy_check(state: AgentState):
    p = state["proposal"]
    reasons = []
    if p["amount"] > MAX_AMOUNT:
        reasons.append("amount is above the hard limit")
    if p["destination"] not in ALLOWLIST:
        reasons.append("destination is not allowlisted")

    return {
        "policy": {
            "allowed": not reasons,
            "reasons": reasons,
        }
    }

def after_policy(state: AgentState):
    return "review" if state["policy"]["allowed"] else "blocked"

def blocked(state: AgentState):
    return {"result": "REJECTED BY POLICY: " + ", ".join(state["policy"]["reasons"])}

قدم چهار: LangGraph وسط کار مکث می‌کنه

interrupt() پیشنهاد رو بیرون می‌فرسته و state رو نگه می‌داره. همون thread_id باعث می‌شه ادامهٔ کار دقیقاً به اجرای قبلی برگرده.

agent.py · review + signer
def human_review(state: AgentState):
    decision = interrupt({
        "question": "Do you approve this demo proposal?",
        "proposal": state["proposal"],
        "allowed_decisions": ["approve", "reject"],
    })
    return {"approved": decision == "approve"}

def signer_boundary(state: AgentState):
    if not state["policy"]["allowed"]:
        return {"result": "REJECTED: policy cannot be bypassed"}
    if not state.get("approved"):
        return {"result": "REJECTED BY HUMAN"}

    # No seed, private key or transaction broadcast exists in this tutorial.
    # This adapter is where demo / Speculos / real hardware would be selected.
    mode = os.getenv("SIGNER_MODE", "demo")
    return {"result": f"APPROVED; handed to {mode} signer boundary (no broadcast)"}

قدم پنج: گراف رو به هم وصل کن

agent.py · graph
builder = StateGraph(AgentState)
builder.add_node("propose", propose)
builder.add_node("policy", policy_check)
builder.add_node("blocked", blocked)
builder.add_node("review", human_review)
builder.add_node("signer", signer_boundary)

builder.add_edge(START, "propose")
builder.add_edge("propose", "policy")
builder.add_conditional_edges(
    "policy",
    after_policy,
    {"review": "review", "blocked": "blocked"},
)
builder.add_edge("blocked", END)
builder.add_edge("review", "signer")
builder.add_edge("signer", END)

graph = builder.compile(checkpointer=InMemorySaver())
config = {"configurable": {"thread_id": "demo-1"}}

first = graph.invoke({
    "request": "Send 0.005 DEMO to demo-alice for lunch"
}, config=config)

if "__interrupt__" in first:
    print(first["__interrupt__"])
    answer = input("Type approve or reject: ").strip().lower()
    final = graph.invoke(Command(resume=answer), config=config)
    print(final["result"])
else:
    print(first["result"])

فایل رو ذخیره کن و اجرا بگیر:

RUN
SIGNER_MODE=demo python agent.py

قدم شش: Speculos دقیقاً کجای داستانه؟

Speculos پروژهٔ رسمی Ledger برای اجرای Ledger Apps روی کامپیوتره. یعنی می‌تونی UI و رفتار یک اپ سازگار رو بدون دستگاه ببینی و تست کنی.

ولی این سخت‌افزار واقعی نیست

Speculos طبق مستندات خودش Secure Element، جداسازی سیستم‌عامل و تمام رفتارهای دستگاه واقعی رو تضمین نمی‌کنه. پس برای آموزش و تست خوبه؛ برای نگه‌داری دارایی یا ادعای امنیت سخت‌افزاری نه.

برای شروع رسمی، مخزن و Quickstart خود Ledger رو دنبال کن. مسیر پایه معمولاً این شکله:

SPECULOS · LEARNING SETUP
git clone https://github.com/LedgerHQ/speculos.git
cd speculos
python3 -m venv .venv
source .venv/bin/activate
pip install .
./speculos.py apps/boil.elf
# then open http://127.0.0.1:5000
روی بعضی سیستم‌ها—مخصوصاً macOS و Windows—Docker یا WSL مسیر ساده‌تریه. Quickstart رسمی رو برای سیستم‌عامل خودت چک کن.
چرا هنوز به فایل app نیاز داریم؟

Speculos خودش «یک کیف پول آماده» نیست؛ مثل یک کنسول بازیه که برای اجرا به بازی نیاز داره. فایل .elf همان Ledger Appیه که داخل emulator اجرا می‌شه.

در پروژهٔ ما، تابع signer_boundary جای اتصال adapter است. فعلاً فقط اعلام می‌کنه پیشنهاد به مرز رسیده. برای اتصال واقعی باید:

مرحلهدر نسخهٔ آموزشیدر نسخهٔ واقعی‌تر
پیشنهاد agentJSON آزمایشیتراکنش decode‌شده و قابل‌نمایش
Policyسقف مبلغ + allowlistsession scope، expiry، nonce و budget
Human reviewapprove/reject در ترمینالنمایش واضح مقصد، مبلغ و معنی عملیات
Speculosتمرین UI یک Ledger AppAPDU adapter سازگار با همان app
Hardwareنداریمکلید داخل دستگاه و تأیید روی secure screen
Broadcastعمداً خاموشبعد از امضای معتبر و کنترل replay

چهار تستی که حتماً باید انجام بدی

درخواست مجاز + approve

باید به signer boundary برسه، ولی در این آموزش هیچ تراکنشی broadcast نمی‌شه.

مبلغ بیشتر از سقف

باید قبل از human review رد بشه؛ آدم نباید مسیر bypass داشته باشه.

مقصد خارج از allowlist

حتی اگر متن درخواست قانع‌کننده بود، policy باید مستقل ردش کنه.

درخواست مجاز + reject

باید با نتیجهٔ REJECTED BY HUMAN تمام بشه و signer صدا نخوره.

تست ایجنت خراب‌شده

تابع propose رو موقتاً طوری تغییر بده که همیشه مبلغ ۹۹۹ بسازه. اگر معماری درست باشه، policy بدون توجه به مدل یا پرامپت، درخواست رو متوقف می‌کنه.

چیزی که ساختیم «کیف پول» نیست؛ یک مرز درست برای agent است

مدل پیشنهاد می‌ده. LangGraph مسیر و توقف رو نگه می‌داره. policy اجازهٔ عبور رو محدود می‌کنه. آدم تصمیم می‌گیره. signer بیرون از runtime ایجنت می‌مونه. Speculos هم کمک می‌کنه تجربهٔ یک Ledger App رو تمرین کنی—نه اینکه امنیت سخت‌افزار واقعی رو جعل کنی.

منابع رسمی و به‌روز

LangGraph Interrupts — pause, checkpoint and resume LangChain Human-in-the-loop middleware LedgerHQ Speculos — official repository, setup and limitations Speculos Quickstart — Linux, macOS and Windows Ollama Quickstart — local model setup

دیسکلیمر: این پروژه برای یادگیری معماری و تست با دادهٔ ساختگیه. seed phrase، private key، دارایی واقعی یا تراکنش واقعی واردش نکن. Speculos جای Secure Element یا دستگاه واقعی نیست. بخش‌هایی از آماده‌سازی و تصویرسازی این راهنما با کمک ایجنت هوش مصنوعی انجام شده.