לדלג לתוכן

7.1 Claude API הרצאה

עד עכשיו בקורס היינו משתמשים - כתבנו prompts ל-Claude דרך צ'אט או דרך tools מוכנים, וקיבלנו תשובות. בפרק הזה אנחנו עוברים צד: ממשתמשים של tool AI לבונים של tool AI. הצעד הראשון והבסיסי ביותר הוא לדבר עם המודל ישירות מתוך קוד שאנחנו כותבים, דרך ה-API. במקום להקליד שאלה בinterface, נשלח בקשת HTTP לשרת של Anthropic, המודל יעבד אותה, ויחזיר תשובה שהתוכנית שלנו יכולה לקרוא ולהמשיך לעבוד איתה. זו הליבה של כל אפליקציית AI - סיווג טקסט, סיכום מסמכים, צ'אטבוט, agent אוטונומי - הכל בסופו של דבר נשען על הקריאה הבסיסית הזו.

בהרצאה הזו נבנה את הבקשה הראשונה שלנו: נתקין את ה-SDK, נבין איך מזדהים עם מפתח API, נכיר את מבנה בקשת ה-Messages, ונקרא את התשובה. נעשה את זה גם בפייתון וגם ב-JavaScript, כי שתי השפות האלה יהיו הבסיס לכל שאר הפרק.


מהו ה-API של Claude - the API

ה-API הוא נקודת קצה אחת מרכזית: POST /v1/messages. שולחים אליה בקשת HTTP עם גוף JSON שמתאר את השיחה, ומקבלים בחזרה JSON עם התשובה של המודל. אפשר לפנות אליה ישירות עם curl, אבל בפועל כמעט תמיד נשתמש ב-SDK רשמי - ספרייה שעוטפת את הבקשות, מטפלת בכותרות, בשגיאות ובחזרות (retries), ומחזירה אובייקטים נוחים.

ל-Anthropic יש שני SDK עיקריים שנשתמש בהם:

  • פייתון - החבילה נקראת anthropic.
  • JavaScript / TypeScript - החבילה נקראת @anthropic-ai/sdk.

חשוב להבין: ה-API הוא חסר מצב (stateless). השרת לא זוכר שיחות קודמות. בכל בקשה אנחנו שולחים את כל היסטוריית השיחה מחדש. זה אולי נשמע בזבזני, אבל זה מה שנותן לנו שליטה מלאה - אנחנו מנהלים את הcontext, לא השרת.


התקנה והזדהות - installation and auth

קודם מתקינים את ה-SDK. בפייתון:

pip install anthropic

וב-JavaScript:

npm install @anthropic-ai/sdk

עכשיו צריך מפתח API. נכנסים ל-Console של Anthropic, יוצרים מפתח, ומעתיקים אותו. לעולם לא כותבים את המפתח בתוך הקוד - זו טעות אבטחה קלאסית שמובילה לדליפת מפתחות ל-GitHub. במקום זה, מגדירים משתנה סביבה:

export ANTHROPIC_API_KEY="sk-ant-..."

ה-SDK יודע לקרוא את המשתנה ANTHROPIC_API_KEY באופן אוטומטי. לכן כשניצור את הלקוח בלי לתת לו מפתח מפורש, הוא ימצא אותו בעצמו מהסביבה. זו הדרך המומלצת.

import anthropic

# The client reads ANTHROPIC_API_KEY from the environment on its own
client = anthropic.Anthropic()

אם ממש חייבים להזריק מפתח ספציפי (למשל בשרת שמנהל כמה מפתחות), אפשר להעביר אותו במפורש - אבל גם אז המפתח מגיע ממשתנה סביבה או ממנהל סודות, לא כמחרוזת קשיחה בקוד:

client = anthropic.Anthropic(api_key=os.environ["MY_KEY"])

הבקשה הראשונה - the Messages API

בואו נשלח את הבקשה הראשונה. שלושה שדות הם חובה בכל בקשה:

  • model - מזהה המודל שנרצה להשתמש בו (מחרוזת).
  • max_tokens - המספר המרבי של tokens שהמודל רשאי לייצר בתשובה.
  • messages - מערך של הודעות השיחה, כל אחת עם role ו-content.

הנה דוגמה מלאה ורצה בפייתון:

import anthropic

client = anthropic.Anthropic()

response = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=1024,
    messages=[
        {"role": "user", "content": "מה בירת צרפת? ענה במשפט אחד."}
    ],
)

# The response is a list of content blocks. We'll print the text
for block in response.content:
    if block.type == "text":
        print(block.text)

מה קורה כאן? יצרנו לקוח, קראנו ל-messages.create, והעברנו את שלושת השדות. השרת החזיר אובייקט תשובה, וה-content שלו הוא רשימה של בלוקים - לא סתם מחרוזת. עוברים על הבלוקים, ומדפיסים את אלה מסוג text.

אותה בקשה בדיוק ב-JavaScript:

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic(); // reads ANTHROPIC_API_KEY from the environment

const response = await client.messages.create({
  model: "claude-opus-4-8",
  max_tokens: 1024,
  messages: [
    { role: "user", content: "מה בירת צרפת? ענה במשפט אחד." },
  ],
});

// iterate over the content blocks and print the text
for (const block of response.content) {
  if (block.type === "text") {
    console.log(block.text);
  }
}

שימו לב שגם ב-JavaScript שמות השדות בגוף הבקשה הם max_tokens ו-messages - אלה שמות ה"חוט" (wire) של ה-API, ולכן הם זהים בשתי השפות.


מבנה ההודעות - the messages array

השדה messages הוא לב העניין. כל הודעה היא אובייקט עם שני שדות:

role     - who is speaking: "user" or "assistant" (the model)
content  - the content: a plain string, or a list of content blocks

כמה כללים חשובים:

  • ההודעה הראשונה חייבת להיות מ-user.
  • בשיחה רב-תורית מוסיפים לסירוגין user ו-assistant.
  • כדי לתת למודל "אישיות" או prompts כלליות, משתמשים בשדה נפרד בשם system (לא הודעה בתוך המערך).

הנה שיחה עם prompt מערכת והיסטוריה. שימו לב שהמודל לא זוכר את התור הקודם - אנחנו מזינים אותו בעצמנו בחזרה:

response = client.messages.create(
    model="claude-opus-4-8",
    max_tokens=1024,
    system="אתה מורה לפיזיקה. הסבר בפשטות, בעברית.",
    messages=[
        {"role": "user", "content": "מה זה כוח הכבידה?"},
        {"role": "assistant", "content": "כוח הכבידה הוא המשיכה בין גופים בעלי מסה."},
        {"role": "user", "content": "ולמה אנחנו לא עפים מהכדור?"},
    ],
)

כדי לבנות צ'אט אמיתי, בכל תור נוסיף את הודעת המשתמש החדשה למערך, נשלח את כל המערך, ואז נוסיף את תשובת המודל למערך לקראת התור הבא. הניהול הזה נמצא כולו בידינו.


קריאת התשובה - reading the response

אובייקט התשובה מכיל הרבה יותר מהטקסט. הנה השדות החשובים:

שדה משמעות
content רשימת בלוקי התוכן (טקסט, קריאות לtools, ועוד)
stop_reason למה המודל הפסיק לייצר
usage כמה tokens נצרכו בקלט ובפלט
model המודל שבאמת ענה
id מזהה ייחודי לבקשה

השדה stop_reason חשוב במיוחד. הערכים הנפוצים:

end_turn     the model finished naturally
max_tokens   the output budget ran out - the response was cut off! need to increase max_tokens
tool_use     the model wants to call a tool (we'll learn this in the next lecture)
refusal      the model refused for safety reasons

אם stop_reason הוא max_tokens, התשובה קטועה באמצע - זו טעות נפוצה של מתחילים שנותנים max_tokens נמוך מדי. בדיקה של השדה הזה חוסכת באגים.

השדה usage חשוב לעלויות. התמחור הוא לפי מיליון tokens, בנפרד לקלט ולפלט:

print(response.usage.input_tokens)   # input tokens
print(response.usage.output_tokens)  # output tokens
print(response.stop_reason)          # end_turn, max_tokens, ...

בחירת מודל - choosing a model

יש כמה מודלים, ולכל אחד איזון שונה בין יכולת, מהירות ומחיר. המזהים המדויקים (נכון להיום):

מודל מזהה - model id מתי להשתמש
Claude Opus 4.8 claude-opus-4-8 המשימות הכי מורכבות, agents, קוד
Claude Sonnet 5 claude-sonnet-5 איזון טוב לעומסי ייצור גבוהים
Claude Haiku 4.5 claude-haiku-4-5 משימות פשוטות ומהירות, סיווג

כלל אצבע: מתחילים עם המודל החזק (Opus) כדי לוודא שהמשימה בכלל אפשרית, ואז יורדים למודל זול יותר אם האיכות מספיקה. משתמשים תמיד במחרוזת המזהה בדיוק כפי שהיא בטבלה - בלי להוסיף סיומות תאריך.


טיפול בשגיאות - error handling

בקשות רשת נכשלות לפעמים. ל-SDK יש מחלקות חריגה (exceptions) ממוקדות, ועדיף לתפוס אותן לפי סוג ולא לפי טקסט ההודעה. הנה שלד טיפוסי:

import anthropic

client = anthropic.Anthropic()

try:
    response = client.messages.create(
        model="claude-opus-4-8",
        max_tokens=1024,
        messages=[{"role": "user", "content": "שלום"}],
    )
except anthropic.AuthenticationError:
    print("The API key is wrong or missing")
except anthropic.RateLimitError:
    print("We exceeded the rate limit - need to wait and try again")
except anthropic.APIStatusError as e:
    print(f"Server error: {e.status_code}")

חדשות טובות: ה-SDK כבר מנסה שוב באופן אוטומטי (retry) על שגיאות זמניות כמו חריגת קצב (429) ושגיאות שרת (5xx), עם השהיה מדורגת. לכן ברוב המקרים לא צריך לכתוב לוגיקת ניסיון-חוזר בעצמנו.


הזרמה - streaming

כשהתשובה ארוכה, המתנה לכל הטקסט בבת אחת גורמת לחוויה איטית - וגם מסתכנת בפסק זמן (timeout) של הבקשה. הפתרון הוא הזרמה: מקבלים את התשובה token-by-token תוך כדי שהמודל מייצר אותה, בדיוק כמו שרואים בinterface הצ'אט. משתמשים ב-messages.stream:

with client.messages.stream(
    model="claude-opus-4-8",
    max_tokens=2048,
    messages=[{"role": "user", "content": "כתוב סיפור קצר על רובוט."}],
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)

כלל פשוט: לכל בקשה עם max_tokens גדול (מעל 16,000 בערך), עדיף להזרים. אם רק צריך את התשובה השלמה בסוף, אפשר לקרוא ל-stream.get_final_message() וכך מקבלים הגנה מפני פסקי זמן בלי לטפל בכל אירוע בנפרד. ההזרמה שולחת אירועים (events) מסוגים שונים - התחלת בלוק, קטע (delta), סיום בלוק - אבל ברוב המקרים מספיק לרוץ על stream.text_stream ולקבל את הטקסט בלבד.


בניית שיחה רב-תורית - a chat loop

כדי לבנות צ'אט אמיתי, מנהלים את המערך של ההודעות בעצמנו. בכל תור: מוסיפים את הודעת המשתמש, שולחים את כל המערך, ואז מוסיפים את תשובת המודל לקראת התור הבא. הנה מחלקה קטנה שעושה בדיוק את זה:

import anthropic

class Conversation:
    def __init__(self, system=None):
        self.client = anthropic.Anthropic()
        self.system = system
        self.messages = []   # the history - we manage it ourselves

    def send(self, user_text):
        self.messages.append({"role": "user", "content": user_text})
        response = self.client.messages.create(
            model="claude-opus-4-8",
            max_tokens=1024,
            system=self.system,
            messages=self.messages,   # the entire history every time
        )
        reply = next(b.text for b in response.content if b.type == "text")
        self.messages.append({"role": "assistant", "content": reply})
        return reply

chat = Conversation(system="אתה עוזר ידידותי. ענה בעברית.")
print(chat.send("קוראים לי אמית."))
print(chat.send("איך קוראים לי?"))   # the model "remembers" - because we sent the history

הנקודה החשובה: המודל לא זוכר כלום בין הקריאות. מה שנותן לו את היכולת "לזכור" את השם הוא אנחנו ששולחים את כל ההיסטוריה מחדש בכל תור. זה גם אומר שככל שהשיחה מתארכת, כל בקשה נהיית גדולה ויקרה יותר - נושא שנטפל בו בהרצאה על מטמון prompts.


ספירת tokens ועלות - token counting

לפני שמריצים בקשה יקרה, לפעמים כדאי לדעת כמה אסימונים היא תעלה. יש נקודת קצה ייעודית לספירה, בלי לשלם על יצירה:

count = client.messages.count_tokens(
    model="claude-opus-4-8",
    messages=[{"role": "user", "content": long_text}],
)
print(count.input_tokens)   # the number of input tokens

חשוב: ספירת tokens היא ספציפית למודל - כל מודל סופר קצת אחרת, ולכן מעבירים את אותו מזהה מודל שנשתמש בו. אל תשתמשו בtools כמו tiktoken (שהוא של OpenAI) - הוא ייתן ספירה שגויה עבור Claude.


סיכום

בהרצאה הזו עשינו את המעבר ממשתמשי AI לבוני AI, דרך הבקשה הראשונה מול ה-API:

  • ה-API של Claude הוא נקודת קצה אחת, POST /v1/messages, שאליה פונים דרך SDK רשמי - anthropic בפייתון, @anthropic-ai/sdk ב-JavaScript.
  • הזדהות נעשית עם מפתח API שמאוחסן במשתנה הסביבה ANTHROPIC_API_KEY - אף פעם לא קשיח בקוד.
  • בקשת Messages דורשת שלושה שדות: model, max_tokens, ו-messages. הprompts הכלליות עוברות בשדה נפרד, system.
  • ה-API חסר מצב - בכל בקשה שולחים את כל היסטוריית השיחה מחדש, וניהול הcontext בידינו.
  • התשובה מכילה content (רשימת בלוקים), stop_reason (בדקו אותו - max_tokens אומר שהתשובה נחתכה) ו-usage (עלויות).
  • בחירת מודל בין claude-opus-4-8, claude-sonnet-5 ו-claude-haiku-4-5 - איזון בין יכולת, מהירות ומחיר.
  • טיפול בשגיאות לפי מחלקות חריגה ממוקדות, והזרמה לתשובות ארוכות כדי לשפר חוויה ולמנוע פסקי זמן.

עכשיו שאנחנו יודעים לשלוח בקשה ולקרוא תשובה, הצעד הבא הוא לתת למודל לא רק לדבר, אלא לפעול - להריץ tools שאנחנו מגדירים. זה הנושא של ההרצאה הבאה.