درس ۲ از ۱۱

Accessing Claude with the API

عنوان اصلی: Module 2 — Accessing Claude with the API هدف یادگیری: ساخت یک سرویس Python پایان‌به‌پایان که از API key محافظت می‌کند، multi-turn است، system prompt دارد، temperature را کنترل می‌کند، streaming پشتیبانی می‌کند، و خروجی JSON ساخت‌یافته تولید می‌کند. مفاهیم کلیدی: ANTHROPIC_API_KEY، workspaces، messages.create، model، max_tokens، messages، system، multi-turn history، stop_sequences، streaming، SSE، event types، JSON schema، messages.parse()، output_config.format (به‌علاوه‌ی مفاهیم legacy: assistant prefill، temperature، top_p).

۲.۱ گرفتن یک API key — workspace، rotation، secret hygiene

API keyها در Console قرار دارند: platform.claude.com/settings/keys. هر key متعلق به یک workspace است — workspaceها به شما اجازه می‌دهند خرج خود را پارتیشن‌بندی کنید (per-product، per-environment، per-customer) و سقف rate-limit و spend را به‌صورت مستقل اعمال کنید. سازمان‌های جدید در tier 1 شروع می‌کنند (RPM/TPM کوچک)؛ tierها به‌صورت خودکار با مصرف و پرداخت به‌موقع ارتقا می‌یابند.

تیم‌های production باید این چهار قاعده را رعایت کنند:

  1. هر environment یک workspace جداگانه داشته باشد (dev, staging, prod).
  2. هر CI runner یک key منحصر بفرد بگیرد.
  3. keyها روی یک schedule چرخش (rotation) داشته باشند.
  4. هرگز key داخل git commit نشود — از .env به اضافه‌ی یک secret manager استفاده کنید.

SDK به‌طور خودکار ANTHROPIC_API_KEY را از environment می‌خواند، بنابراین کد application شما تمیز می‌ماند. برای موارد استفاده در browser، هرگز key را سمت کلاینت expose نکنید — درخواست‌ها را از طریق backend خودتان proxy کنید تا key (و spend) روی سرور شما باقی بماند.

مثال عملی

# .env (never committed)
export ANTHROPIC_API_KEY="sk-ant-api03-..."
# Code never sees the key directly
from anthropic import Anthropic
client = Anthropic()  # picks up ANTHROPIC_API_KEY automatically

# For multi-tenant SaaS, scope a request with metadata.user_id
client.messages.create(
    model="claude-haiku-4-5",
    max_tokens=256,
    metadata={"user_id": "tenant_42_user_007"},
    messages=[{"role": "user", "content": "ping"}],
)

اشتباهات رایج

  • commit کردن .env به git (راه حل: .gitignore + push-protection گیت‌هاب).
  • اشتراک یک key در همه‌ی محیط‌ها (revoke دقیق غیرممکن می‌شود).
  • ندادن metadata.user_id (Anthropic از آن برای abuse detection استفاده می‌کند — بدون آن، کل سازمان شما ممکن است به‌خاطر یک tenant خاطی rate-limit شود).

۲.۲ اولین درخواست — ساختار حداقلی Messages API

Messages API به‌طور غیرعذرخواهانه minimal است. یک درخواست معتبر فقط به سه فیلد نیاز دارد: model، max_tokens، و یک آرایه‌ی غیرخالی messages. در پاسخ، یک object از نوع Message می‌گیرید با: id، role: assistant، content (آرایه‌ای از blockها، حتی اگر فقط یک خط متن باشد)، stop_reason و usage.

تنها بخش کمی غافلگیرکننده، آرایه‌ی content است: حتی یک پاسخ تک‌خطی به‌صورت [{"type": "text", "text": "..."}] بسته‌بندی می‌شود. این یکپارچگی بعدها به دردتان می‌خورد: tool calls، thinking blocks، و citations همه به‌صورت نوع‌های block اضافی روی همین آرایه می‌نشینند.

max_tokens اجباری است و حد بالای output (نه total) را تعیین می‌کند. آن را سخاوتمندانه بگذارید — یک پاسخ ناتمام با stop_reason: "max_tokens" هم token هایی که پولش را داده‌اید هدر می‌دهد، هم یک round-trip را. Anthropic ماکزیمم‌های هر مدل را منتشر می‌کند (مدل‌های فعلی تا ۱۲۸K token خروجی، Haiku 4.5 تا ۶۴K؛ برای max_tokens بالاتر از حدود ۱۶K، streaming الزامی است). همیشه بعد از فراخوانی usage را بازرسی کنید: input_tokens + output_tokens همان چیزی است که صورت‌حساب می‌گیرید؛ cache_read_input_tokens و cache_creation_input_tokens (ماژول ۷) اثربخشی cache را نشان می‌دهند.

مثال عملی

import anthropic
client = anthropic.Anthropic()

resp = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=1024,
    messages=[{"role": "user", "content": "What is the capital of France?"}],
)

print("id:        ", resp.id)
print("model:     ", resp.model)
print("stop:      ", resp.stop_reason)         # 'end_turn'
print("input toks:", resp.usage.input_tokens)
print("output toks:", resp.usage.output_tokens)
for block in resp.content:
    if block.type == "text":
        print(block.text)

معادل curl (مفید برای debug از پشت یک corporate proxy):

curl https://api.anthropic.com/v1/messages \
  -H "x-api-key: $ANTHROPIC_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "model": "claude-opus-4-8",
    "max_tokens": 1024,
    "messages": [{"role": "user", "content": "What is the capital of France?"}]
  }'

اشتباهات رایج

  • فراموش کردن max_tokens (درخواست رد می‌شود).
  • خواندن resp.text به‌جای iterate کردن روی resp.content (دومی contract رسمی است).
  • این فرض که stop_reason: "end_turn" یعنی «بدون خطا» — refusal هم یک HTTP 200 موفق است.

۲.۳ مکالمه‌ی چند‌نوبتی — تمرین stateful بودن سمت اپلیکیشن

API به‌طور کامل stateless است. هر فراخوانی باید کل مکالمه را شامل شود. Anthropic بین درخواست‌ها هیچ‌چیز ذخیره نمی‌کند. statefulness کار application شماست — معمولاً یک list از dictهای {"role": ..., "content": ...} که در حافظه نگه داشته می‌شود یا روی database مبتنی بر session id persist می‌شود.

نقش‌ها باید به‌ترتیب user → assistant → user → assistant … و شروع از user باشند. دو turn متوالی با نقش یکسان به‌طور صامت ادغام می‌شوند. سقف درخواست ۱۰۰٬۰۰۰ پیام است، اما سقف عملی، context-window budget شماست (۱M token برای مدل‌های فعلی، ۲۰۰k برای Haiku 4.5). مکالمات بلند به یکی از این سه راهکار نیاز دارند:

  1. خلاصه‌سازی (summarization) turnهای قدیمی‌تر.
  2. RAG برای آوردن فقط slice مرتبط.
  3. prompt caching روی پیشوند ثابت (ماژول ۷).

یک ترفند قدیمی این دوره‌ها prefill بود: یک پیام assistant تا حدی پر شده در انتهای messages که Claude از همان نقطه ادامه می‌داد. روی مدل‌های نسل فعلی (Fable 5 و کل خانواده‌ی 4.6/4.7/4.8 و Sonnet 5) این الگو حذف شده و خطای 400 برمی‌گرداند. راه امروزی اجبار فرمت خروجی، structured outputs با output_config.format است (درس ۲.۹).

مثال عملی

history = []

def chat(user_text: str) -> str:
    history.append({"role": "user", "content": user_text})
    resp = client.messages.create(
        model="claude-opus-4-8",
        max_tokens=1024,
        messages=history,
    )
    answer = resp.content[0].text
    history.append({"role": "assistant", "content": answer})
    return answer

print(chat("My name is Maria."))
print(chat("What's my name?"))   # uses history -> 'Maria'

جایگزین امروزی prefill، structured outputs است:

resp = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=512,
    messages=[{"role": "user", "content": "Return city and country for Tehran."}],
    output_config={"format": {"type": "json_schema", "schema": {
        "type": "object",
        "properties": {"city": {"type": "string"}, "country": {"type": "string"}},
        "required": ["city", "country"], "additionalProperties": False,
    }}},
)
print(resp.content[0].text)  # guaranteed schema-valid JSON

اشتباهات رایج

  • فراموش کردن append کردن turn assistant به history (Claude context قبلی خودش را گم می‌کند).
  • تلاش برای prefill با پیام assistant ناتمام روی مدل‌های فعلی (خطای 400؛ به‌جای آن structured outputs استفاده کنید).
  • اجازه دادن history به رشد بی‌حد (cap بگذارید، خلاصه کنید، یا RAG کنید).

۲.۴ ساخت یک chat-bot — تمرین کاربردی

فراتر از حلقه‌ی پایه، یک chat-bot با کیفیت تولیدی به چهار قطعه نیاز دارد:

  1. یک history bounded — turnهای قدیمی را drop کنید یا خلاصه کنید وقتی token count به ۷۵٪ context نزدیک می‌شود.
  2. metadata.user_id به ازای هر کاربر، برای abuse-detection و rate-limit per-tenant.
  3. مدیریت خطای تمیز برای anthropic.APIError، RateLimitError و OverloadedError با exponential backoff.
  4. یک object پیکربندی typed تا model و max_tokens بدون تغییر کد قابل tune باشند.

این تمرین همچنین system prompt را به‌عنوان پیش‌نمایش معرفی می‌کند — حتی یک system prompt یک‌خطی («You are a concise assistant; reply in <=3 sentences.») تجربه‌ی کاربری bot را به‌طور چشمگیری تغییر می‌دهد.

مثال عملی

from anthropic import Anthropic, RateLimitError, APIStatusError
import time, sys

client = Anthropic()
SYSTEM = "You are a concise assistant. Reply in at most three sentences."
MODEL = "claude-haiku-4-5"
MAX_HISTORY_TOKENS = 30_000

def trim(history):
    # Drop oldest pairs when token estimate exceeds budget
    while sum(len(m["content"].split()) for m in history) * 1.3 > MAX_HISTORY_TOKENS:
        del history[0:2]
    return history

def safe_call(history, retries=3):
    for i in range(retries):
        try:
            return client.messages.create(
                model=MODEL, max_tokens=512, system=SYSTEM, messages=history,
            )
        except (RateLimitError, APIStatusError) as e:
            time.sleep(2 ** i)
    raise

def main():
    history = []
    while True:
        try:
            user = input("you> ").strip()
        except (EOFError, KeyboardInterrupt):
            print(); break
        if not user: continue
        if user in {"/exit", "/quit"}: break
        history.append({"role": "user", "content": user})
        resp = safe_call(trim(history))
        answer = resp.content[0].text
        history.append({"role": "assistant", "content": answer})
        print(f"bot> {answer}")

if __name__ == "__main__":
    main()

اشتباهات رایج

  • catch کردن همه‌ی exceptionها و retry صامت (bugهای واقعی پنهان می‌شوند).
  • truncate کردن وسط turn (همیشه به‌صورت زوج user/assistant drop کنید).
  • فراموش کردن flush=True هنگام streaming.

۲.۵ System prompts — تنظیم نقش، صدا و محدودیت‌ها

system یک پیام نیست — یک پارامتر سطح بالا است. این فیلد، persona و دستورالعمل‌های دائمی Claude را قبل از شروع هر مکالمه تعیین می‌کند. استفاده از آن (به‌جای چپاندن دستورالعمل‌ها در اولین پیام user) سه مزیت دارد:

  1. جداسازی تمیز concerns (دستورالعمل‌ها در برابر ورودی کاربر).
  2. Claude train شده تا دستورالعمل‌های system prompt را با وزن بیشتری نسبت به دستورالعمل‌های inline دنبال کند.
  3. می‌توانید blockهای system را با cache_control نشان‌گذاری کنید تا یک prompt ثابت بزرگ فقط یک بار با قیمت کامل در هر cache window صورت‌حساب شود (ماژول ۷).

system promptهای موثر چهار جزء دارند: role («You are a senior tax attorney...»)، task («...specializing in Iranian VAT compliance»)، constraints («Always cite article numbers; refuse to give individualized advice»)، و format («Reply in markdown with headings»). راهنمای best-practices Anthropic، role + clarity را قوی‌ترین تکنیک بعد از نوشتن evalهای خوب می‌داند.

مثال عملی

فرم string (ساده) و فرم آرایه (cacheable):

# Simple string system prompt
client.messages.create(
    model="claude-opus-4-8",
    max_tokens=1024,
    system="You are a senior tax attorney. Cite article numbers in every answer.",
    messages=[{"role": "user", "content": "Is freight subject to VAT?"}],
)

# Array form unlocks per-block cache_control
client.messages.create(
    model="claude-opus-4-8",
    max_tokens=1024,
    system=[
        {"type": "text", "text": "You are a senior tax attorney."},
        {
            "type": "text",
            "text": LONG_TAX_CODE_TEXT,
            "cache_control": {"type": "ephemeral"},   # cache this huge block
        },
    ],
    messages=[{"role": "user", "content": "Is freight subject to VAT?"}],
)

اشتباهات رایج

  • گذاشتن دستورالعمل‌ها در اولین user message به‌جای system (پایبندی ضعیف‌تر + قابل cache جدا نیست).
  • تناقض با خود (مثلاً «be concise» + «always include all considerations»).
  • این فرض که system یک turn حساب می‌شود — نمی‌شود. قاعده‌ی alternation فقط روی messages اعمال می‌شود.

۲.۶ تمرین System prompt — تخصصی کردن chat-bot برای یک vertical

این تمرین، chat-bot ساده‌ی قبلی را به یک دستیار vertical تبدیل می‌کند. system promptهای production واقعی معمولاً ۲۰۰ تا ۲٬۰۰۰ token طول دارند و مانند یک شرح شغل برای یک کارمند junior خوانده می‌شوند: شما کی هستید، به چه کسی خدمت می‌کنید، چه کاری می‌توانید/نمی‌توانید انجام دهید، چگونه باید بیان کنید، قوانین escalation.

دو الگوی کم‌استفاده اما قدرتمند:

  1. یک knowledge base کوچک برای FAQها در system prompt جاسازی کنید، و بعد آن را cache کنید.
  2. یک راه فرار (out) ارائه دهید: «اگر کاربر پرسید درباره چیزی خارج از SKU lookup، پاسخ بده 'I can only help with product questions; please email support@…'» تا refusalها graceful شوند.

برای deployment فارسی/RTL، language pinning اهمیت دارد: دستورالعمل‌های صریح اضافه کنید مانند «همیشه به فارسی استاندارد پاسخ بده. اعداد باید با ارقام فارسی ۰-۹ نوشته شوند.» بدون این pinning، Claude اغلب برای اصطلاحات فنی به انگلیسی switch می‌کند.

مثال عملی — دستیار فارسی برای فروشگاه

SYSTEM_FA = """\
شما دستیار مشتری‌مداری برای فروشگاه آنلاین «پیپا لندن ایران» هستید.
نقش: پاسخ به سوالات درباره محصولات، سفارش‌ها، و سیاست بازگشت کالا.
محدودیت: درباره موضوعات خارج از فروشگاه پاسخ ندهید؛ با جمله "متاسفم، فقط می‌توانم درباره محصولات پیپا کمک کنم." پاسخ دهید.
لحن: مودبانه، صمیمی، حداکثر سه جمله.
زبان: همیشه فارسی استاندارد. اعداد را با ارقام فارسی (۰-۹) بنویسید.
"""

resp = client.messages.create(
    model="claude-haiku-4-5", max_tokens=300,
    system=SYSTEM_FA,
    messages=[{"role": "user", "content": "آیا ارسال به مشهد رایگان است؟"}],
)
print(resp.content[0].text)

اشتباهات رایج

  • ترکیب کردن دستورالعمل‌های meta انگلیسی («Always be concise») با persona فارسی — Claude گاهی زبان دستورالعمل meta را آینه می‌کند. system prompt را به‌طور کامل به زبان هدف بسازید.

۲.۷ Temperature — کنترل تصادفی بودن (فقط مدل‌های قدیمی‌تر)

نکته‌ی مهم ۲۰۲۶: پارامترهای sampling (temperature، top_p، top_k) روی مدل‌های نسل فعلی حذف شده‌اند. Fable 5 و Opus 4.8/4.7 آن‌ها را با خطای 400 رد می‌کنند و Sonnet 5 مقدار غیر پیش‌فرض را نمی‌پذیرد. این درس برای Haiku 4.5 و خانواده‌ی 4.6 و مدل‌های قدیمی‌تر همچنان کاربردی است؛ روی مدل‌های فعلی، کنترل خروجی را با prompt و structured outputs انجام دهید.

temperature یک عدد اعشاری بین ۰.۰ و ۱.۰ است (پیش‌فرض API برابر ۱.۰). در ۰.۰، decoding تقریباً deterministic است — Claude در هر گام، token با بالاترین احتمال را انتخاب می‌کند. در ۱.۰، توزیع بدون flatten کردن sample می‌شود.

دو پیش‌فرض عملیاتی:

  • ۰.۰ برای کارهای تحلیلی / استخراج / classification (جایی که reproducibility و دقت بر تنوع برتری دارند).
  • ۰.۷ تا ۱.۰ برای کارهای خلاقانه / brainstorming / drafting.

top_p (nucleus sampling — نگه داشتن tokenهایی که جمع احتمال آن‌ها ≤ p است) و top_k (نگه داشتن top-k token) دستگیره‌های پیشرفته‌ای هستند که Anthropic توصیه می‌کند تا زمانی‌که تعامل آن‌ها با temperature را عمیقاً نفهمیده‌اید، رهایشان کنید. تنظیم همزمان temperature و top_p یک foot-gun رایج است.

حتی در temperature 0، دو درخواست یکسان می‌توانند پاسخ‌های کمی متفاوت تولید کنند، چون GPU خودش غیر deterministic است. اگر replay دقیق bit-by-bit می‌خواهید، evalها را batch کنید و خروجی‌ها را ذخیره کنید.

مثال عملی

# Analytical: classify with temperature 0
client.messages.create(
    model="claude-haiku-4-5", max_tokens=8, temperature=0,
    messages=[{"role": "user", "content": "Classify: 'This phone is amazing!' -> positive/negative/neutral"}],
)

# Creative: brainstorm with temperature 1 (sampling params: 4.6-era and older only)
client.messages.create(
    model="claude-sonnet-4-6", max_tokens=512, temperature=1,
    messages=[{"role": "user", "content": "Brainstorm 5 names for a Persian RTL spreadsheet plugin."}],
)

اشتباهات رایج

  • فرستادن temperature به Fable 5 یا Opus 4.8/4.7 (خطای 400؛ این مدل‌ها پارامتر sampling نمی‌پذیرند).
  • استفاده از temperature 1 برای code generation (bug تولید می‌کند).
  • استفاده از temperature 0 برای کارهای خلاقانه (خسته‌کننده و تکراری).
  • تغییر همزمان temperature و top_p (تعامل غیرشهودی).

۲.۸ Streaming — تجربه‌ی time-to-first-token پایین

Streaming، یک HTTP response چاق را با یک stream طولانی Server-Sent Events (SSE) عوض می‌کند. هر event یک object JSON با فیلد type است. lifecycle سطح بالا چنین است:

message_start  → metadata (id، model، usage اولیه)
  content_block_start
    content_block_delta  (تکراری — text_delta یا input_json_delta)
  content_block_stop
  ... (blockهای بیشتر)
message_delta  → stop_reason نهایی + usage تجمعی
message_stop

داخل text content blockها، deltaها از نوع text_delta هستند؛ داخل blockهای tool_use، deltaها از نوع input_json_delta هستند (شما با concat کردن stringهای نسبی، JSON را دوباره می‌سازید).

Python SDK تمام این پیچیدگی را در دو شکل ergonomic می‌پیچاند:

  1. یک generator sync/async از طریق messages.create(stream=True) — شما روی eventهای خام iterate می‌کنید.
  2. یک context manager سطح بالاتر از طریق client.messages.stream(...) که stream.text_stream (فقط chunkهای متن) و helperهایی مثل stream.get_final_message() را expose می‌کند.

برای chat UIها از context manager استفاده کنید؛ برای کنترل دقیق هنگام render کردن thinking، tool calls یا citations زنده، از event stream خام استفاده کنید.

مثال عملی

فرم context-manager (توصیه‌شده):

with client.messages.stream(
    model="claude-opus-4-8",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Write a haiku about Tehran."}],
) as stream:
    for chunk in stream.text_stream:
        print(chunk, end="", flush=True)
    final = stream.get_final_message()
    print(f"\n[stop_reason={final.stop_reason}, "
          f"out_tokens={final.usage.output_tokens}]")

eventهای خام (وقتی نیاز به کنترل دقیق دارید — مثل streaming روی tool):

resp = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=1024, stream=True,
    messages=[{"role": "user", "content": "..."}],
)
for event in resp:
    if event.type == "content_block_delta":
        if event.delta.type == "text_delta":
            print(event.delta.text, end="", flush=True)
        elif event.delta.type == "input_json_delta":
            buffer.append(event.delta.partial_json)   # for tool calls
    elif event.type == "message_stop":
        break

اشتباهات رایج

  • فراموش کردن flush=True در print (خروجی به‌صورت تکه‌تکه ظاهر می‌شود به‌خاطر line-buffering Python).
  • مدیریت نکردن error events (یک خطای وسط stream هم به‌صورت SSE می‌رسد).
  • concat کردن stringهای input_json_delta بدون buffer مناسب (نمی‌توانید روی string نسبی json.loads کنید).

۲.۹ کنترل خروجی — prefill، stop sequences و structured outputs

چهار اهرم، به ترتیب پیچیدگی:

  1. دستورالعمل‌های صریح در system یا user prompt («پاسخ را فقط با یک کلمه: yes یا no بده»).
  2. prefill — یک پیام assistant تا حدی پر شده که Claude از آن ادامه می‌داد (فقط مدل‌های قدیمی؛ روی مدل‌های نسل فعلی خطای 400 برمی‌گرداند).
  3. stop_sequences — تا ۴ string که اگر Claude منتشر کند، تولید پایان می‌یابد (Claude stop_reason: "stop_sequence" و stop_sequence: "<which one>" برمی‌گرداند).
  4. structured outputs از طریق output_config.format با یک JSON Schema (یا helper SDK یعنی messages.parse() که یک Pydantic model می‌گیرد و یک object typed برمی‌گرداند).

structured outputs گزینه‌ی production-grade است: schema validation در حین decoding توسط Anthropic اعمال می‌شود، بنابراین دیگر نیاز نیست فراخوانی‌ها را داخل retry logic برای JSON malformed بپیچید. caveatها: حداکثر ۲۰ tool strict در هر درخواست، حداکثر ۲۴ پارامتر اختیاری، بدون recursive schema، بدون bound عددی (minimum, maximum)، و ناسازگار با citations.

مثال عملی

# 1. Stop sequences — get a single line
resp = client.messages.create(
    model="claude-haiku-4-5", max_tokens=64,
    stop_sequences=["\n"],
    messages=[{"role": "user", "content": "What is 2+2? Reply with just the number."}],
)

# 2. Structured outputs (raw JSON Schema)
resp = client.messages.create(
    model="claude-opus-4-8", max_tokens=512,
    messages=[{"role": "user", "content":
        "Extract: John Smith (john@x.com), Enterprise plan, demo Tue 2pm."}],
    output_config={"format": {"type": "json_schema", "schema": {
        "type": "object",
        "properties": {
            "name": {"type": "string"},
            "email": {"type": "string"},
            "plan_interest": {"type": "string"},
            "demo_requested": {"type": "boolean"},
        },
        "required": ["name", "email", "plan_interest", "demo_requested"],
        "additionalProperties": False,
    }}},
)

# 3. Pydantic helper
from pydantic import BaseModel
class Contact(BaseModel):
    name: str; email: str; plan_interest: str; demo_requested: bool

parsed = client.messages.parse(
    model="claude-opus-4-8", max_tokens=512,
    messages=[{"role": "user", "content": "Same input as above."}],
    output_format=Contact,
)
print(parsed.parsed_output.email)

اشتباهات رایج

  • استفاده از prefill روی مدل‌های فعلی (خطای 400؛ prefill فقط روی مدل‌های قدیمی‌تر از خانواده‌ی 4.6 کار می‌کند).
  • فعال کردن همزمان structured outputs و citations (پاسخ 400 می‌گیرید).
  • اولین درخواست با schema جدید کند است چون grammar compilation کمی زمان می‌برد (۲۴ ساعت بعد cache می‌شود).

۲.۱۰ تولید داده‌ی ساخت‌یافته — تمرین + quiz

تمرین پایان ماژول، استخراج فیلدهای ساخت‌یافته از رسیدها یا صفحات محصول فارسی است. pipeline کامل:

  1. system prompt نقش + زبان را شفاف می‌کند.
  2. user message متن خام را شامل می‌شود.
  3. output_config.format شکل را اعمال می‌کند.
  4. temperature=0 تصادفی بودن را حذف می‌کند (فقط روی مدل‌هایی که پارامتر sampling دارند، مثل Haiku 4.5؛ مدل‌های فعلی Opus و Fable این پارامتر را نمی‌پذیرند).
  5. کد downstream JSON اعتبارسنجی‌شده را بدون try/except مصرف می‌کند.

سه ترفند reliability فراتر از schema پایه:

  • یک فیلد confidence اضافه کنید تا LLM بتواند extractionهای کم‌کیفیت را خودش flag کند.
  • برای فیلدهای دسته‌ای، enum صریح بگذارید (مثلاً currency: ["IRR", "USD", "EUR"]).
  • روی propertyها از description استفاده کنید — Claude آن را به‌عنوان دستورالعمل inline می‌خواند.

quiz ماژول ۲ معمولاً مقادیر stop_reason، جایگذاری system در برابر messages، چیزی که max_tokens واقعاً محدود می‌کند، و سلسله‌مراتب رویدادهای SSE را آزمون می‌گیرد.

مثال عملی — استخراج رسید فارسی

schema = {
    "type": "object",
    "properties": {
        "vendor":   {"type": "string", "description": "Store or seller name"},
        "total":    {"type": "number", "description": "Total in IRR (no commas)"},
        "currency": {"type": "string", "enum": ["IRR", "USD", "EUR"]},
        "items": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "name": {"type": "string"},
                    "qty":  {"type": "integer"},
                    "price":{"type": "number"},
                },
                "required": ["name", "qty", "price"],
                "additionalProperties": False,
            },
        },
        "confidence": {"type": "number", "description": "0-1; how sure are you?"},
    },
    "required": ["vendor", "total", "currency", "items", "confidence"],
    "additionalProperties": False,
}

resp = client.messages.create(
    model="claude-haiku-4-5", max_tokens=1024, temperature=0,
    system="Extract receipt fields. Respond only with the JSON object.",
    messages=[{"role": "user", "content": persian_receipt_text}],
    output_config={"format": {"type": "json_schema", "schema": schema}},
)

اشتباهات رایج

  • رد کردن additionalProperties: false (مدل کلیدهای اضافی اختراع می‌کند).
  • استفاده از temperature > 0 برای استخراج (typoهای ناسازگار وارد می‌کند).
  • فراموش کردن این‌که PHI داخل descriptionهای schema برای grammar caching لاگ می‌شود — شناسه‌ها را از descriptionها بزدایید.

ادامه‌ی این فصل در فایل 04-building-claude-api-fa-part2.md: ماژول‌های ۳ تا ۱۰ — Prompt Evaluation، Prompt Engineering Techniques، Tool Use، RAG and Agentic Search، Features of Claude (prompt caching، extended thinking، vision، files)، Anthropic Apps، و Agents and Workflows.