خواندنِ پاسخ
برنامهی سلامِ لیلا کار میکند. از سرِ کنجکاوی خطِ آخر را به print(reply) تغییر میدهد و به جای یک سلام، یک صفحهی پر میبیند: یک شناسه، نامِ مدل، چیزی به نامِ content که دو چیز درونش است، یک stop_reason و یک usage با چهار عدد. او فقط یک جمله خواسته بود. بقیهاش چیست؟
لیلا انتظار داشتم رشته باشد. چرا content فهرست است؟
امید غیر از کلمههای جواب، دیگر چه چیزی دیدهای که مدل بنویسد؟
لیلا فکر کردن، پیش از جواب. و در عامل، درخواست برای استفاده از ابزار.
امید خب، اگر پاسخ یک رشته بود، برنامهات اینها را چطور از هم جدا میکرد؟
لیلا نمیتوانست، دستکم نه با اطمینان. باید حدس میزد فکر کردن کجا تمام میشود و درخواستِ ابزار کجا شروع میشود.
امید برای همین پاسخ تکهتکه و برچسبخورده میآید. هر تکه یک بلوک است با یک type، و برنامهات نوعهایی را که لازم دارد برمیدارد.
لیلا آن عددهای پایین چه؟
امید آن صورتحساب است. هر پاسخ دقیقاً میگوید پولِ چه چیزی را از تو گرفتهاند.
بلوکهای محتوا
reply.content فهرستی از بلوکهای محتوا (content blocks) است. هر بلوک یک type دارد و بقیهی بخشهایش به نوعش بستگی دارد:
"text": کلمههای جواب، درblock.text. یک پاسخ میتواند بیش از یک بلوکِ متن داشته باشد."thinking": کارِ ذهنیِ مدل پیش از جواب دادن. Opus 5.5 همیشه فکر میکند، پس پاسخهایش معمولاً با یکی از این بلوکها شروع میشود. متنِ آن بهطور پیشفرض نمیآید (میشود خلاصهاش را خواست)، ولی پولِ فکر کردن را به هر حال میپردازید."tool_use": درخواستی برای اجرای یک ابزار، باnameابزار وinputآن. اینها را وقتی میبینید که به mini ابزار بدهید، دو بخش بعد.
برای همین برنامهی سلام روی بلوکها میچرخد و فقط بلوکهای متن را نگه میدارد. همین کار را کوتاهتر هم میشود نوشت، و mini همهجا همین را به کار میبرد:
answer = "".join(b.text for b in reply.content if b.type == "text")دقت کنید که این کد هیچوقت فرض نمیکند متن بلوکِ اول است، یا فقط یک بلوکِ متن هست. کدی که reply.content[0].text مینویسد تا روزی کار میکند که بلوکِ اول یک بلوکِ فکر کردن باشد، و آن روز خراب میشود.
چرا ایستاد
reply.stop_reason (دلیلِ ایستادن) میگوید مدل چرا از نوشتن دست کشید. چهار مقدار برای یک عامل مهم است:
"end_turn": مدل جوابش را تمام کرد. حالتِ عادی همین است."max_tokens": پاسخ به سقفِmax_tokensرسید و بریده شد، شاید وسطِ یک جمله."tool_use": مدل ایستاد تا یک ابزار اجرا شود. حلقهای که در بخشهای بعد مینویسید روی همین میچرخد."refusal": مدل به دلیلهای ایمنی از جواب دادن خودداری کرد.
برنامهی دقیق پیش از آنکه به محتوا اعتماد کند، stop_reason را نگاه میکند. پاسخی که max_tokens آن را بریده میتواند تمامشده به نظر برسد، و کدی که آن را تمامشده حساب کند بر اساسِ نصفِ یک جواب کار میکند.
چقدر هزینه داشت
reply.usage (مصرف) عددهایی را نگه میدارد که صورتحساب از آنها ساخته میشود:
input_tokens: توکنهایی که فرستادید و با قیمتِ کاملِ ورودی پردازش شدند.cache_read_input_tokensوcache_creation_input_tokens: توکنهایی که از کشِ پرامپت خوانده یا در آن نوشته شدند. فعلاً صفرند. در یکی از بخشهای بعد کش را روشن میکنید و اینها بزرگ میشوند.output_tokens: هر چه مدل نوشت، فکر کردن هم جزوِ آن است.
هر نوع قیمتِ خودش را برای هر یک میلیون توکن دارد. در اواخرِ ۲۰۲۶، برای Opus 5.5: ورودی ۴ دلار، خروجی ۲۰ دلار، خواندن از کش ۰٫۲۰ دلار (یکبیستمِ قیمتِ ورودی) و نوشتن در کش ۵ دلار. Sonnet 5.5 برای ورودی و خروجی ۲ و ۱۰ دلار میگیرد و Haiku 5.5، ۰٫۱۰ و ۰٫۵۰ دلار.
در کد، این یک جدول است و یک جمع:
PRICES = {"input": 4.00, "output": 20.00, "cache_read": 0.20, "cache_write": 5.00}
def cost(usage):
u = usage
dollars = (u.input_tokens * PRICES["input"]
+ (u.cache_read_input_tokens or 0) * PRICES["cache_read"]
+ (u.cache_creation_input_tokens or 0) * PRICES["cache_write"]
+ u.output_tokens * PRICES["output"])
return dollars / 1_000_000
print(f"{cost(reply.usage):.6f} dollars")or 0 برای این است که وقتی چیزی برای شمردن نبوده، یک عدد میتواند None باشد. مثالِ تصویر یک قدمِ معمولی از جلسهی کار با عامل است: بیشترِ ورودی از کش میآید و ارزان است، در حالی که ۱٬۵۰۰ توکنِ خروجی بیش از نیمی از صورتحساب را میسازد. خروجی نوعِ گران است، و فکر کردن هم خروجی است.
لیلا پس عاملِ من میتواند صورتحسابِ خودش را همینطور که پیش میرود نگه دارد.
امید باید هم نگه دارد. بعد از هر فراخوانی cost() را جمع کن تا همیشه بدانی یک کار چقدر خرج داشت و کدام بخشش گران بود.
لیلا اگر پاسخی output_tokens خیلی بزرگی داشت ولی جوابش کوتاه بود؟
امید یعنی بیشترش فکر کردن بوده. این را هم خوب است بدانی: یا کار برای مدل سخت بوده، یا میزانِ تلاش را بیشتر از لازم گذاشتهای.
امتحان کنید
اگر کلید دارید،
print(reply.stop_reason, reply.usage)را به برنامهی سلام اضافه کنید. یک بار با «Say hello» اجرایش کنید و یک بار با یک معمای کوچک: «یک چوب و یک توپ روی هم ۱٫۱۰ دلار میارزند و چوب ۱ دلار از توپ گرانتر است. توپ چند است؟»output_tokensدو اجرا را مقایسه کنید: فرقشان فکر کردن است. بعدmax_tokens=20بگذارید، یک داستانِ بلند بخواهید وstop_reasonرا نگاه کنید. اگر کلید ندارید، تمرینِ بعد از این درس همین حساب را روی پاسخهایی شبیهِ پاسخهای واقعی انجام میدهد.
mini تا اینجا
در پایانِ هر بخش، کلِ mini را همانطور که هست میبینید، تا همیشه یک نسخهی سالم برای کپی کردن داشته باشید. بعد از این بخش، mini یک فراخوانی است و پاسخ را درست میخواند:
mini.py بعد از «نخستین فراخوانی»
import anthropic
MODEL = "claude-opus-5-5" # claude-haiku-5-5 is about 40x cheaper for practice
client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY
reply = client.messages.create(
model=MODEL,
max_tokens=1000,
messages=[{"role": "user", "content": "Say hello in one short sentence."}],
)
print("".join(b.text for b in reply.content if b.type == "text"))
print(reply.stop_reason, reply.usage)درست به کار بردن
- بلوکها را با نوعشان انتخاب کنید، هرگز با جایشان.
if b.type == "text"با هر ترتیبی از بلوکها کار میکند.content[0]با اولین بلوکِ فکر کردن خراب میشود. - پیش از عمل کردن به پاسخ،
stop_reasonرا نگاه کنید.max_tokensرا ناتمام بدانید وrefusalرا بیجواب، نه پاسخی عادی. - مصرفِ هر فراخوانی را ثبت کنید. این اطلاعات مجانی است، و وقتی جلسهای بیش از انتظار خرج داشت، مصرف نشان میدهد پول کجا رفته است.
- عددِ ناموجود را صفر حساب کنید. بنویسید
(u.field or 0)، تا پاسخی که در آن کشی در کار نبوده کدِ هزینهتان را خراب نکند.
واژههای این درس
- بلوکِ محتوا (content block) - یک تکهی برچسبخورده از پاسخ، مثلِ متن، فکر کردن یا فراخوانیِ ابزار، که با
typeاز بقیه جدا میشود. - دلیلِ ایستادن (stop reason) - اینکه مدل چرا از نوشتن ایستاد: کارش تمام شد، توکنش تمام شد، منتظرِ ابزار است، یا خودداری کرد.
- مصرف (usage) - شمارِ توکنهای یک پاسخ (ورودی، خواندن و نوشتن در کش، خروجی)، که هزینهی پاسخ از آنها ساخته میشود.
ایدهی اصلی
پاسخ از تکههای برچسبخورده، یک دلیلِ ایستادن و یک صورتحساب ساخته شده است. هارنس هر سه را میخواند: بلوکها را تا بداند مدل چه گفت یا چه خواست، دلیلِ ایستادن را تا بداند کارش تمام شده یا نه، و مصرف را تا بداند چقدر خرج داشت.