Knaph

نخستین فراخوانی2 از 376 دقیقه مطالعه

خواندنِ پاسخ

برنامه‌ی سلامِ لیلا کار می‌کند. از سرِ کنجکاوی خطِ آخر را به 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 ابزار بدهید، دو بخش بعد.

شیءِ پاسخ. در آن response.content است، فهرستی از بلوک‌ها: یک بلوکِ فکر کردن که به‌طور پیش‌فرض پنهان است و یک بلوکِ متن با پاسخ. کنارش stop_reason برابرِ end_turn است و usage که می‌گوید ۱۲ توکن ورودی و ۸۰ توکن خروجی مصرف شد.

برای همین برنامه‌ی سلام روی بلوک‌ها می‌چرخد و فقط بلوک‌های متن را نگه می‌دارد. همین کار را کوتاه‌تر هم می‌شود نوشت، و mini همه‌جا همین را به کار می‌برد:

answer = "".join(b.text for b in reply.content if b.type == "text")
Python

دقت کنید که این کد هیچ‌وقت فرض نمی‌کند متن بلوکِ اول است، یا فقط یک بلوکِ متن هست. کدی که 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، ۰٫۱۰ و ۰٫۵۰ دلار.

از مصرف تا دلار. هر ردیفِ usage در قیمتِ خودش برای هر میلیون توکن ضرب می‌شود: ۲٬۰۰۰ توکنِ ورودی در ۴ دلار، ۸۰٬۰۰۰ توکنِ خواندن از کش در ۰٫۲۰ دلار، ۰ توکنِ نوشتن در کش در ۵ دلار و ۱٬۵۰۰ توکنِ خروجی در ۲۰ دلار. جمع حدود ۰٫۰۵۴ دلار است.

در کد، این یک جدول است و یک جمع:

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")
Python

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)
Python

درست به کار بردن

  1. بلوک‌ها را با نوعشان انتخاب کنید، هرگز با جایشان. if b.type == "text" با هر ترتیبی از بلوک‌ها کار می‌کند. content[0] با اولین بلوکِ فکر کردن خراب می‌شود.
  2. پیش از عمل کردن به پاسخ، stop_reason را نگاه کنید. max_tokens را ناتمام بدانید و refusal را بی‌جواب، نه پاسخی عادی.
  3. مصرفِ هر فراخوانی را ثبت کنید. این اطلاعات مجانی است، و وقتی جلسه‌ای بیش از انتظار خرج داشت، مصرف نشان می‌دهد پول کجا رفته است.
  4. عددِ ناموجود را صفر حساب کنید. بنویسید (u.field or 0)، تا پاسخی که در آن کشی در کار نبوده کدِ هزینه‌تان را خراب نکند.

واژه‌های این درس

  • بلوکِ محتوا (content block) - یک تکه‌ی برچسب‌خورده از پاسخ، مثلِ متن، فکر کردن یا فراخوانیِ ابزار، که با type از بقیه جدا می‌شود.
  • دلیلِ ایستادن (stop reason) - اینکه مدل چرا از نوشتن ایستاد: کارش تمام شد، توکنش تمام شد، منتظرِ ابزار است، یا خودداری کرد.
  • مصرف (usage) - شمارِ توکن‌های یک پاسخ (ورودی، خواندن و نوشتن در کش، خروجی)، که هزینه‌ی پاسخ از آن‌ها ساخته می‌شود.

ایده‌ی اصلی

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

دانش خود را بسنجید

ورود برای سنجش دانش خود از این درس و پیگیری پیشرفتتان.