کانتکست تنها منبعی است که ایجنت از آن میخواند و همیشه گرانترین بخش صورتحساب است. راهبرد درست این نیست که کانتکست را بزرگ کنید؛ این است که بفهمید هر توکنی که در آن میماند چند بار پرداخت میشود. در این نوشته سه جای خرج را با شمارش توکن اندازه میگیرم: ۲۴۴۷ توکنی که پیش از پیام شما و در هر نوبت ارسال میشود، تاریخچهای که هر نوبت از نو پرداخت میشود، و خواندن بیحد که یک فایل ۹٬۶۰۰ بایتی را ۴۰۰ برابر بزرگتر از نیاز نشان میدهد. همهی عددها خروجی واقعی ابزار روی همین ماشین است.
بیشتر گفتوگو دربارهی پنجرهی زمینه اشتباه یک جای مشخص است: فرض میکند مسئله این است که مدل چقدر میتواند بخواند. این عدد روی کاغذ ثابت است و شما کاری با آن نمیکنید. آنچه شما کنترل میکنید این است که چه چیزی وارد میشود و چه چیزی میماند، و این تنها جایی است که صرفهجویی ممکن است. نوشتهی توکن و پنجرهی زمینه از سمت مدل همین عدد ثابت را باز میکند؛ اینجا از سمت صورتحساب جلو میرویم.
پس بهجای بحث دربارهی اندازهی پنجره، کانتکست را به سه لایه بشکنید. لایهی اول آن چیزی است که همیشه هست و هرگز انتخاب نمیشود. لایهی دوم تاریخچهی گفتوگو است که با هر نوبت دوباره فرستاده میشود. لایهی سوم کاری است که خود شما با یک ابزار انجام میدهید و میتوانید انجام ندهید. هزینهی شما جمع این سه است، و هر کدام رفتار متفاوتی دارند.
لایهی اول: آنچه پیش از پیام شما میآید
کدکس یک فرمان دارد که ورودی مدل را پیش از ارسال نشان میدهد. این تنها راه دیدن لایهی اول است، چون هیچ ابزار دیگری آن را به شما نشان نمیدهد. خروجی، فهرستی از بلوکهای متنی است که هر درخواست همراه دارد.
<span class="code-comment"># در یک ریپازیتوری واقعی، پیش از آنکه پیام شما ارسال شود چه چیزی میرود؟</span>
$ codex debug prompt-input "سلام"
[
{ "type": "message", "role": "developer", "content": [
{ "text": "<skills_instructions>..." },
{ "text": "<permissions instructions>..." },
{ "text": "<collaboration_mode>..." },
{ "text": "<multi_agent_role>..." },
{ "text": "<multi_agent_mode>..." },
{ "text": "<environment_context>..." } ] },
{ "role": "user", "content": [ { "text": "سلام" } ] }
]
حالا همان خروجی را با یک توکنشمار واقعی میشماریم. کتابخانهی tiktoken با رمزگذار o200k_base همان چیزی را میشمارد که مدل میبیند، پس این عدد حدس نیست.
<span class="code-comment"># resident_ctx.py -- شمارش لایهی همیشهحاضر</span>
import json, os, subprocess
import tiktoken
ENC = tiktoken.get_encoding("o200k_base")
CX = "codex"
def toks(s: str) -> int:
return len(ENC.encode(s, disallowed_special=()))
out = subprocess.run([CX, "debug", "prompt-input", "سلام"],
capture_output=True, env=dict(os.environ, CODEX_HOME="/tmp/cx"),
timeout=120)
items = json.loads(out.stdout)
total = 0
for item in items:
for part in item.get("content", []):
n = toks(part.get("text", ""))
total += n
head = part.get("text", "").strip().splitlines()[0][:44]
print(f"{n:7d} {head}")
print("جمع:", total, "توکن")
نتیجهی اجرا روی این ماشین، با ۶۹ اسکیل نصبشده، چنین است. سهم هر بلوک جدا اندازهگیری شده و مجموع دقیقاً با جمع تکتک سطرها میخواند.
| بلوک | توکن | سهم |
|---|---|---|
| دسترسیها و قواعد سندباکس | ۸۴۳ | ۳۴٫۴٪ |
| نقش چندعاملی | ۵۳۵ | ۲۱٫۹٪ |
| فهرست اسکیلها | ۵۵۵ | ۲۲٫۷٪ |
| حالت همکاری | ۲۴۹ | ۱۰٫۲٪ |
| بافت محیط | ۲۱۲ | ۸٫۷٪ |
| یادداشت حالت چندعاملی | ۵۲ | ۲٫۱٪ |
| پیام شما | ۱ | ۰٫۰٪ |
| جمع | ۲۴۴۷ | ۱۰۰ |
پنجرهی کاربردی این مدل از فهرست مدل میآید: ۲۷۲٬۰۰۰ توکن پنجرهی خام و ۹۵ درصد قابل استفاده، یعنی ۲۵۸٬۴۰۰ توکن. پس لایهی ثابت ۰٫۹۵ درصد پنجره را میگیرد. در یک نشست پنجاهنوبتی، همان ۲۴۴۷ توکن هر بار دوباره میآید و جمع ورودی میشود با ۱۲۲٬۳۵۰ توکن، یعنی ۴۷٫۳ درصد پنجره. این عدد مهم است: لایهی ثابت در یک کار طولانی از یک اسکیل بزرگ هم گرانتر تمام میشود.
نکتهی دوم این جدول، تفکیکی است که معمولاً گم میشود. تنها ۵۵۵ توکن از کل، فهرست اسکیلهاست، و آن هم فقط نام و توضیح کوتاه. بدنهی ۶۹ اسکیل نصبشده ۱۸۵٬۴۲۸ توکن دیگر است؛ یعنی توضیحها ۰٫۶۸ درصد حجم کلاند. اگر همهی بدنهها همزمان بارگذاری شوند، کاتالوگ کامل ۱۸۶٬۶۹۵ توکن میشود: ۹۳٫۳ درصد یک پنجرهی ۲۰۰ هزارتایی، و بیش از صد درصد پنجرهی ۱۲۸ هزارتایی. در پست هزینهی پنهان هر اسکیل همین تفکیک را از سمت اسکیلهای دستی اندازه گرفته شده است.
پس معماری لایهبندی درست است و مشکل ندارد. نکته این است که هزینهی لایهی ثابت از جنس تعداد ابزار و اندازهی توضیحهاست، نه از جنس حجم کاری که انجام میدهید. کاهش آن یعنی کم کردن تعداد اسکیلهای نصبشده و کوتاه کردن توضیحها، که هر دو کار ارزانی است و یک بار برای همیشه انجام میشود. مسیر نصب همین تعداد است: هر افزونهای که نصب میکنید چیزی به این لایه اضافه میکند، و مرجع خط فرمان افزونهها دستوری دارد که سهم هر افزونه را جدا نشان میدهد.
لایهی دوم: تاریخچهای که دوباره پرداخت میشود
لایهی دوم رفتار متفاوتی دارد. این لایه یک بار تولید نمیشود؛ در هر نوبت، کل تاریخچه از نو فرستاده میشود. یعنی هر توکنی که در نوبت سوم تولید شده، در نوبت سوم یک بار و در نوبت پنجم دو بار پرداخت شده است.
یک نشست معمولی پنجنوبتی را حساب میکنیم. اندازهی خروجی ابزارها را از یک کار واقعی گرفتهام و هر نوبت را جدا شمردهام.
<span class="code-comment"># هر نوبت کل تاریخچه را دوباره میفرستد؛ این یعنی رشد مربعی</span>
نوبت ۱: خروجی این نوبت 300 انباشته 300 ورودی این نوبت 300
نوبت ۲: خروجی این نوبت 600 انباشته 900 ورودی این نوبت 1,200
نوبت ۳: خروجی این نوبت 975 انباشته 1,875 ورودی این نوبت 3,075
نوبت ۴: خروجی این نوبت 1,050 انباشته 2,925 ورودی این نوبت 6,000
نوبت ۵: خروجی این نوبت 750 انباشته 3,675 ورودی این نوبت 9,675
فقط ۳٬۶۷۵ توکن خروجی ابزار در کل نشست تولید شده، اما ۹٬۶۷۵ توکن ورودی پرداخت شده. ضریب ۲٫۶۳ بهدست آمد، و این ضریب در نوبت دهم از ۲ به بالاتر میرود. اگر کار شما بیست نوبت طول بکشد و هر نوبت بهاندازهی نوبت چهارم خروجی تولید کند، جمع ورودی از ۶ هزار به بیش از ۳۰ هزار میرسد، در حالی که محتوای تازه همان ۲ هزار توکن است.
نتیجهی عملی روشن است: کوتاه کردن تاریخچه فقط وقتی به شما کمک میکند که خروجی ابزار را کم کند، نه وقتی متن گفتوگو را خلاصه کنید. پیام خودتان ارزان است، خروجی ابزار گران است. هر بار که یک ابزار صد خط خروجی برمیگرداند و شما همهی آن را میخوانید، هزینهی آن صد خط در همهی نوبتهای بعدی است، نه یک بار.
لایهی سوم: خواندن بیحد
سومین جای خرج، رفتار ابزارهای خواندن است. یک فایل معمولی را کامل میخوانید چون ابزار اجازه میدهد، و بعد میفهمید تنها هشت خط اولش لازم بوده است. نسبت این دو، عددی است که هر بار تکرار میشود.
<span class="code-comment"># یک فایل، دو شیوهی خواندن، با همان توکنشمار</span>
>>> toks("def handler(request):\n return jsonify(ok)\n" * 400)
3600
>>> toks("def handler(request):\n return jsonify(ok)\n")
9
نسبت: 400 برابر
چهارصد برابر، برای فایلی که در هر دو حالت همان پاسخ را دارد. اگر در نشستی پنج نوبت از چنین ابزاری استفاده کنید، همان خواندن بیحد ۱۸٬۰۰۰ توکن ورودی میسازد در حالی که نسخهی محدودش ۴۵ توکن است. این بدترین نسبت در کل سه لایه است، چون برخلاف لایهی اول، اینجا شما هر بار دوباره همان اشتباه را تکرار میکنید. پروتکلهای سروری هم همین هزینه را دارند: چون نتیجهی هر فراخوانی یک بار تولید و در همهی نوبتهای بعدی دوباره پرداخت میشود، حتی متن خطا هم یک لایهی دوم است و راهنمای مدیریت خطا دقیقاً همین تفکیک را میکند که کدام متن از خطای ابزار است و کدام از خطای خود پروتکل.
سه قاعدهی عملی از همین اندازهگیری بیرون میآید، و هر سه ارزاناند:
- پیش از خواندن، الگو بپرسید نه کل فایل را. جستوجوی یک رشته معمولاً یک درصد از حجم فایل را برمیگرداند.
- از بازه و سقف استفاده کنید. خواندن با offset و limit روی همان فایل، ۹ توکن برمیگرداند نه ۳٬۶۰۰.
- خروجی حجیم را به فایل بنویسید، نه به کانتکست. یک دستور که نتیجه را در پرونده میریزد و فقط مسیر را برمیگرداند، بهترین شکل ممکن است.
ترتیب این سه اهمیت دارد. اولی ارزانترین است و بیشترین اثر را دارد، چون جلوی ورود داده را میگیرد. دومی وقتی لازم است که مطمئنید کجا را نگاه کنید. سومی آخرین راه است و فقط وقتی جواب میدهد که مطمئنید باز هم به آن فایل برمیگردید.
یک تصحیح لازم است، چون در مقایسههای رایج اشتباه میشود: خلاصه کردن پیامهای خودتان کماثرترین گزینه است. متن شما در مقایسه با خروجی ابزار و لایهی ثابت، بخش کوچکی از صورتحساب است. صرفهجویی روی جایی که چند درصد از هزینه است، در حالی که نود درصد جای دیگر است، بیاثر میماند.
اولین کار: کف هزینه را اندازه بگیرید
اگر فقط یک کار بکنید، لایهی ثابت را اندازه بگیرید. با فرمانی که در بالا آمد، خودتان ببینید چند توکن پیش از پیامتان میآید و چند نوبت معمولاً کار شما طول میکشد. حاصلضرب این دو، کف هزینهی هر نشست است؛ هر چیزی که فراتر از آن پرداخت میشود، جایی است که تاریخچه یا ابزار تصمیم گرفته است. اگر ابزار خودتان را مینویسید، بستهای که آن را نصب میکند هم بخشی از همین لایهی ثابت است: بستهی mcp و مخزن رسمی sdk هر دو با هر بار بهروزرسانی، کاتالوگ ابزار شما را عوض میکنند.
همین روش برای هر هارنسی که استفاده میکنید کار میکند، چون همهی آنها یک لایهی ثابت دارند و هیچکدام از شما اجازهی حذفش را نمیدهید. این لایه هرجا هارنسی از شما تنظیمات میگیرد ساخته میشود، و راهنمای پروندههای تنظیمات توضیح میدهد کدام فایل تعیین میکند کدام تنظیم برنده باشد. تنها تفاوت این است که در کدکس میتوانید آن لایه را ببینید و بشمارید. در بقیه، همان کار را با تخمین انجام دهید و دستکم بدانید کف هزینه چقدر است.
منابع
- بستهی mcp در PyPI — نسخهها و کف پایتون
- مخزن رسمی sdk
- صفحهی انتشارها — اندازهی هر تغییر در کاتالوگ ابزار
- مدیریت خطا در سرور — چرا متن خطا هم یک لایه هزینه است
- مشخصات پروتکل — تعریف ابزار و نتیجهی آن
- چه چیزی تازه شده
- مستندات کلاینت
- نسخههای پروتکل
- راهنمای مهاجرت
- بستهی httpx — وابستگی زنجیرهای که هر بهروزرسانی به لایهی ثابت اضافه میکند
- بستهی mcp-types
- مرجع خط فرمان افزونهها — سهم هر افزونه در لایهی ثابت
- پروندههای تنظیمات و ترتیب اولویت
توضیح کش پرامپت خیلی روشن بود. یک سوال: اگر پیشوند ثابت بماند ولی ابزارها عوض شوند، کش باطل میشود؟