بهترین راه فهمیدن عامل‌ها، ساختن یکی است — و خبر خوب این است که عامل «حداقلی» با حدود صد خط کد ساخته می‌شود؛ نه فریم‌ورک سنگین، نه زیرساخت خاص. در این آموزش یک عامل واقعی می‌سازیم: عاملِ «تحلیلگر فروش» که می‌تواند فایل CSV فروش را بخواند، سؤال‌های آماری را با اجرای کد جواب دهد و گزارش بسازد. در پایان، همان چارچوب برای هر ابزار دیگری — دیتابیس، API داخلی، وب — قابل استفاده است.

طراحی: سه ابزار و یک حلقه

به عامل خودمان سه ابزار می‌دهیم: read_csv (خواندن فایل و دیدن ستون‌ها)، run_python (اجرای کد پایتون روی داده و گرفتن خروجی) و write_report (ذخیره‌ی گزارش نهایی). عامل هدف را می‌گیرد، خودش برنامه می‌ریزد، ابزارها را به‌ترتیب لازم صدا می‌زند، خروجی هر قدم را می‌بیند و تا تحویل گزارش جلو می‌رود. این همان حلقه‌ی «فکر، اقدام، مشاهده» است که در مطلب عامل چگونه کار می‌کند تحلیلش کردیم.

قدم اول: اتصال به مدل

با API سازگار با OpenAI ماین‌دبیس شروع می‌کنیم — همان کلاینت استاندارد، فقط آدرس و کلید متفاوت:

from openai import OpenAI

client = OpenAI(
    base_url="https://api.mindbase.ir/v1",
    api_key="YOUR_API_KEY",
)

MODEL = "glm-5.3"

قدم دوم: تعریف ابزارها

هر ابزار یک تابع پایتون واقعی است به‌علاوه‌ی «توضیح رسمی» برای مدل. کیفیت همین توضیح‌ها، کیفیت انتخاب ابزار توسط مدل را تعیین می‌کند — مبهم‌نویسی این‌جا یعنی عاملِ گیج:

import pandas as pd

def read_csv(path):
    df = pd.read_csv(path)
    return f"ستون‌ها: {list(df.columns)} | تعداد ردیف: {len(df)}\n{df.head(3).to_string()}"

def run_python(code):
    import io, contextlib
    buf = io.StringIO()
    df = globals().get("_df")
    try:
        with contextlib.redirect_stdout(buf):
            exec(code, {"df": df, "pd": pd})
        out = buf.getvalue() or "(بدون خروجی)"
        return out[:2000]
    except Exception as e:
        return f"خطا: {e}"

def write_report(text):
    with open("report.md", "w", encoding="utf-8") as f:
        f.write(text)
    return "گزارش ذخیره شد: report.md"

TOOLS = [
    {"type": "function", "function": {
        "name": "read_csv",
        "description": "فایل CSV را می‌خواند؛ ستون‌ها، تعداد ردیف و سه ردیف اول را برمی‌گرداند. همیشه اول این را صدا بزن.",
        "parameters": {"type": "object", "properties": {
            "path": {"type": "string", "description": "مسیر فایل CSV"}}}}},
    {"type": "function", "function": {
        "name": "run_python",
        "description": "کد پایتون اجرا می‌کند؛ دیتافریم با نام df در دسترس است. خروجی چاپ‌شده برمی‌گردد. حداکثر ۲۰۰۰ کاراکتر چاپ کن.",
        "parameters": {"type": "object", "properties": {
            "code": {"type": "string", "description": "کد پایتون"}}}}},
    {"type": "function", "function": {
        "name": "write_report",
        "description": "متن گزارش نهایی را در فایل ذخیره می‌کند. فقط وقتی تحلیل کامل است.",
        "parameters": {"type": "object", "properties": {
            "text": {"type": "string", "description": "متن کامل گزارش"}}}}},
]

قدم سوم: حلقه‌ی اجرا

حالا قلب عامل: پیام‌ها را به مدل می‌دهیم؛ اگر مدل «فراخوانی ابزار» خواست، اجرایش می‌کنیم و نتیجه را برمی‌گردانیم؛ این چرخه تا وقتی ادامه دارد که مدل پاسخ نهایی بدهد:

import json

def run_agent(goal, max_steps=15):
    messages = [
        {"role": "system", "content":
         "تو تحلیلگر داده‌ی دقیقی. فقط از ابزارهای موجود استفاده کن. "
         "قبل از نتیجه‌گیری همیشه اعداد را با run_python راستی‌آزمایی کن. "
         "اگر داده برای پاسخ ناکافی است، صریح بگو چه چیزی کم است."},
        {"role": "user", "content": goal},
    ]
    dispatch = {"read_csv": read_csv, "run_python": run_python, "write_report": write_report}
    for step in range(max_steps):
        resp = client.chat.completions.create(
            model=MODEL, messages=messages, tools=TOOLS)
        msg = resp.choices[0].message
        messages.append(msg)
        if not msg.tool_calls:
            return msg.content
        for call in msg.tool_calls:
            args = json.loads(call.function.arguments)
            result = dispatch[call.function.name](**args)
            messages.append({"role": "tool",
                             "tool_call_id": call.id,
                             "content": str(result)})
    return "به سقف قدم‌ها رسیدم؛ خلاصه‌ی وضعیت: " + messages[-1]["content"]

print(run_agent("فایل sales.csv را تحلیل کن: مجموع فروش، پرفروش‌ترین محصول، و روند سه ماه آخر؛ گزارش را ذخیره کن."))

چه اتفاقی در اجرا می‌افتد؟

عامل اول read_csv را صدا می‌زند تا شکل داده را ببیند؛ بعد با run_python محاسبات را یکی‌یکی انجام و اعداد را چک می‌کند؛ اگر کدی خطا بدهد، پیام خطا را می‌بیند و کد را اصلاح می‌کند — همین حلقه‌ی خطا/اصلاح است که عامل را از یک اسکریپت ثابت جدا می‌کند؛ در پایان گزارش را با write_report ذخیره می‌کند و برایتان خلاصه می‌نویسد. کل این مسیر شاید پنج تا ده فراخوانی ابزار باشد؛ هزینه‌اش با مدل مناسب چند صدم تومان است.

سه اصل ایمنی که از روز اول رعایت کنید

  1. سقف حلقه: پارامتر max_steps شوخی نیست؛ عامل بدون سقف می‌تواند حلقه‌ی بی‌پایان بسازد و حساب را ببلعد.
  2. حداقل اختیار: ابزار run_python در این مثال عمداً ساده و sandbox-شده است؛ در محیط واقعی، اجرای کد عامل را در محیط ایزوله و بدون دسترسی شبکه انجام دهید.
  3. تأیید انسانی برای کارهای برگشت‌ناپذیر: هر ابزاری که پول حرکت می‌دهد، ایمیل می‌فرستد یا چیزی حذف می‌کند، باید قبل از اجرا تأیید بگیرد — حتی وقتی همه‌چیز خوب کار می‌کند.

از این‌جا به بعد

همین چارچوب قابل گسترش است: ابزارهای بیشتر (دیتابیس، وب، ایمیل)، حافظه‌ی بین‌مأموریتی، و اتصال به ابزارهای استاندارد از طریق MCP به‌جای نوشتن همه‌چیز از صفر. و برای کدنویسی، ابزارهای آماده مثل عامل‌های کدنویس — موضوع مطلب بعدی این مجموعه — همین الگو را در مقیاس بزرگ پیاده کرده‌اند. کلید API را از پنل ماین‌دبیس بگیرید و همین امروز عامل‌تان را اجرا کنید — با اعتبار هدیه، بدون هزینه‌ی اولیه.