ch
Feedback
ToCode

ToCode

前往频道在 Telegram

טיפים קצרים למתכנתים מאת ינון פרק

显示更多
1 419
订阅者
无数据24 小时
无数据7
-430
帖子存档
ToCode
1 419
יום 4 - בואו נכתוב סוכן שיחה אתמול ראינו איך לכתוב תהליך עבודה שמורכב ממספר "סוכנים", כל אחד אחראי לחלק אחר של המשימה. היום נדבר על סוג שני של מערכות משולבות AI שהוא מערכות מבוססות שיחה. במערכות כאלה אין משימה ספציפית שצריך לבצע אלא אנחנו מגיעים לסוכן, שולחים הודעה והסוכן עונה או מבצע ואז מקשיב להמשך השיחה. דוגמאות למערכות כאלה יכולות להיות: 1. ממשק צ'ט עם סוכן חכם כמו באתר ChatGPT. 2. סוכן תמיכה חכם שעונה לפניות של משתמשים במקום נציג אנושי. 3. סוכן מכירות חכם שמתקשר למשתמש ומנהל שיחה כדי למכור מוצר. 4. מורה לאנגלית שמנהל עם משתמש שיעור אנגלית מובנה תוך כדי שיחה. לפני הכל - קוד נתחיל בדוגמת קוד לשיחה עם סוכן חכם משורת הפקודה:
import asyncio

from agents import Agent, Runner, SQLiteSession

async def main():
    agent = Agent(name="Assistant", instructions="Reply very concisely.")

    # Create session instance
    session = SQLiteSession("conversation_123")

    while True:
        user_message = input("> ")
        ai_response = await Runner.run(agent, user_message, session=session)
        print(ai_response.final_output)
        

if __name__ == '__main__':
    asyncio.run(main())
הריצו את הקוד עם משתנה הסביבה OPENAI_API_KEY מכוון למפתח ה API שלכם ותקבלו שיחה עם סוכן חכם. איך זה עובד שורת הקסם בתוכנית היא:
ai_response = await Runner.run(agent, user_message, session=session)
ממשק העבודה עם LLM הוא Stateless, כלומר כל הודעה ל ChatGPT או לכל AI שנבחר מתחילה מאפס. בממשק של סוכן אנחנו רגילים שהסוכן ממשיך את השיחה ובשביל זה הוא צריך להתבסס על ההודעות הישנות. ניהול ההודעות הישנות ושליחתן יחד עם ההודעה החדשה מבוצע בצד הלקוח ובספריית openai-agents זה מנוהל באמצעות מנגנון ה session. כדי להבין את המנגנון בואו נכתוב את הקוד בלי session:
import asyncio

from agents import Agent, Runner, SQLiteSession

async def main():
    agent = Agent(name="Assistant", instructions="Reply very concisely.")
    user_message = input("> ")
    result = await Runner.run(agent, user_message)
    print(result.final_output)

    while True:
        print(f"Debug: messages = {result.to_input_list()}")
        user_message = input("> ")
        result = await Runner.run(agent, result.to_input_list() + [{"role": "user", "content": user_message}])
        print(result.final_output)


if __name__ == '__main__':
    asyncio.run(main())
עכשיו זה ברור: 1. ממשק Runner.run יכול לקבל הודעה בודדת או רשימה של הודעות. 2. הממשק מחזיר אוביקט שמכיל מתודה to_input_list. מתודה זו מחזירה את כל ההודעות הישנות ואת ההודעה האחרונה שה AI השיב. 3. באמצעות שמירת אוביקט התוצאה ושליחת ההודעות הישנות שוב ושוב ל Runner.run אנחנו מקבלים חוויה של שיחה המנוהלת בצד הלקוח. מנגנון Sessions של ספריית הסוכנים מאפשר ניהול אוטומטי של העברת ההודעות הישנות. בדוגמה שראינו ההודעות נשמרות בבסיס נתונים SQLite שנשמר בזיכרון. בהמשך הסידרה נראה איך להשתמש במנגנון גם כדי לשמור את ההודעות בבסיס נתונים אמיתי וגם לערוך הודעות ישנות. עכשיו אתם אחד הדברים שהופכים סוכנים ל"ייחודיים" הוא הודעות המערכת, בדוגמאות הקוד שכתבנו זה מאפיין ה instructions שאנחנו מעבירים לסוכן:
agent = Agent(name="Assistant", instructions="Reply very concisely.")
עדכנו את התוכנית ושנו את מחרוזת ה instructions כדי לייצר סוכנים מסוגים שונים: סוכן שמלמד אנגלית, סוכן מכירות או יועץ לאורח חיים בריא.

ToCode
1 419
title: str = Field(..., title="Title", description="The title of the blog post"),
    content: str = Field(..., title="Content", description="Actual blog post content in markdown format")

async def main(general_topic: str):
    market_research_agent = Agent(
        name="MarketResearcher",
        model=LitellmModel(model="github/gpt-4.1", api_key=os.environ["GITHUB_TOKEN"]),
        output_type=list[BlogPostIdea],
        instructions="""
        You are a market researcher and your job is to suggest cool ideas for blog posts.
        I will send you ideas and you will help me turn them into engaging posts,
        Or I will send you topics and you will help to focus me on the best viral ideas in these niches.        
        """
    )

    result = await Runner.run(market_research_agent, f"Create 5 blog posts subject lines and main concept for: {general_topic}")
    selected_idea = random.sample(result.final_output, 1)[0]

    writer = Agent(
        name="Writer",
        model=LitellmModel(model="github/gpt-4.1", api_key=os.environ["GITHUB_TOKEN"]),
        instructions="""
        You are a copywriter creating engaging and viral blog posts.
        """,
    )
    post = await Runner.run(writer, f"Create a blog post from the following data: {selected_idea.model_dump()}")
    print(post.final_output)

if __name__ == "__main__":
     asyncio.run(main("dogs"))
בשביל להריץ אותו אתם צריכים להגדיר את GITHUB_TOKEN כמו שראינו אתמול או לבחור מודל אחר מ LitellmModel עם מפתח API אחר לבחירתכם. אלה הדברים החשובים בתוכנית: 1. אני יוצר שני סוכנים, כל אחד עם פרומפט הוראות משלו. סוכן חקר השוק אחראי על העלאת רעיונות לפוסטים וסוכן הכתיבה אחראי על כתיבת התוכן. 2. המידע שחוזר מכל סוכן הוא מידע מובנה ולכן אני יכול לבחור פריט אחד באקראי מתוך רשימת פוסטים ולא צריך לפענח את הטקסט שה LLM החזיר. ספריית Agents SDK עושה את הפיענוח עבורי. 3. לסוכן אין זיכרון או מצב פנימי. כל פעם שאני מפעיל Runner.run הוא פונה מחדש למודל השפה הגדול עם הפרומפט שכתבתי ועם פרומפט המערכת ומקבל תשובה. כל בקשה עומדת בפני עצמה. 4. ספריית OpenAI Agents SDK עדיין לא תומכת בהעברת מידע מובנה בתור קלט לפרומפט - זו הסיבה שהשתמשתי ב selected_idea.model_dump() בתוך מחרוזת הפרומפט. פקודה זו הופכת את האוביקט למילון רגיל בפייתון. הפרומפט שמודל השפה הגדול יקבל יכול להיות לכן:
Create a blog post from the following data: {'title': '10 Surprising Ways Dogs Make Our Lives Better', 'main_concepts': ['Emotional and physical benefits of owning a dog', 'How dogs improve mental health', "Lesser-known facts about dogs' positive influence"]}
עכשיו אתם רוצים לשחק עם הדוגמה? מצוין! זו אחלה דרך ללמוד. הוסיפו סוכן "עורך" שישפר את הטקסט ויהפוך אותו ליותר מעניין וגם סוכן "מדרג" שמקבל את הרעיונות ומדרג אותם כדי לבחור את הפוסט המעניין ביותר.

ToCode
1 419
יום 3 - דוגמה לתהליך עבודה של שני סוכנים בפוסט היום נכתוב תהליך עבודה ליצירת פוסט לבלוג. התהליך ישלב שני סוכנים וגם קוד פייתון רגיל ודרך הדוגמה נלמד איך נראה תהליך אוטומטי מבוסס AI, מה סוג החיבור שאנחנו צריכים בין קוד ה AI לקוד הפייתון ואיפה משתלבת ספריית הסוכנים. מה אנחנו בונים בשביל להתחיל לדבר על תהליכי עבודה משולבי AI אני רוצה לאפיין תהליך פשוט של יצירת פוסט לבלוג המורכב משני סוכנים וקוד פייתון באמצע. שלבי התהליך: 1. מודל שפה גדול מקבל משימה למצוא רעיונות לפוסטים. הוא מחזיר רשימה של 5 רעיונות. 2. פונקציית פייתון בוחרת את אחד הרעיונות, במקרה שלנו זו פשוט בחירה באקראי אבל בעולם האמיתי אולי היא תסתכל ב DB להבין איזה פוסטים עדיין לא כתבתי או תחפש ברשת או כל מימוש אחר. 3. אחרי שהחלטנו על נושא מודל שפה גדול (יכול להיות אותו מודל או מודל אחר) מקבל את המשימה לכתוב פוסט מפורט לבלוג על נושא זה. 4. קוד פייתון מקבל את הפוסט ומפרסם אותו. בדוגמה שלנו ה"פרסום" זה בסך הכל הדפסה למסך, אבל נוכל לדמיין שאנחנו מעלים את הפוסט לבלוג. נשים לב לסעיפים 1 ו-3 ברשימה שלי: 1. שני הסעיפים מבצעים משימה באמצעות מודל שפה גדול, אבל הם לא חייבים להשתמש באותו מודל. למעשה הרבה פעמים בכתיבת תהליך עבודה נרצה לבחור לכל חלק בתהליך את המודל הטוב ביותר עבורו, וזה לא תמיד יהיה אותו מודל. אין מודל אחד הכי טוב שעושה הכל ובבניית מערכת אג'נטית אנחנו משחקים, מנסים ובוחרים ולפעמים גם מתחרטים. 2. אחרי שמודל השפה הגדול החליט על רעיונות לפוסטים אני צריך לקבל את המידע שחזר ולעבוד איתו מתוך פייתון. יהיה לי נוח אם המידע הזה יגיע בתור מבנה נתונים פייתונאי רגיל. וכבר אנחנו רואים את התפקיד של ספריית OpenAI Agents SDK במערכת שלנו. הספריה תאפשר לי להגדיר שני "סוכנים", לבחור לכל סוכן מודל שפה גדול שמתאים לו, לבחור לכל סוכן פרומפט מערכת שונה שמתאים לו והכי חשוב לקבל מהמודל מידע מובנה בפורמט של מחלקה בפייתון. הגדרת טיפוס חזרה ספריית OpenAI Agents SDK אבל גם ספריות רבות אחרות לעבודה עם מודלי שפה גדולים בפייתון משתמשת ב Pydantic כדי להגדיר מידע מובנה שאנחנו מצפים לקבל חזרה מהמודל. דוגמה ראשונה לקלאס פידנטיק עבור תהליך כתיבת הפוסט תהיה:
from pydantic import BaseModel, Field

class BlogPostIdea(BaseModel):
    title: str = Field(..., title="Title", description="The title of the blog post"),
    main_concepts: list[str] = Field(..., title="Main Concepts", description="Main concepts for the post")
פידנטיק היא ספריית פייתון לתיאור ואימות מבני נתונים. היא מאפשרת לי להגדיר מחלקה ולקבוע שיהיו בה שני שדות, title ו main_concepts, לתאר מה הסוג של כל שדה וגם מה תפקידו. כל התיאורים האלה עוזרים למודל השפה הגדול להבין איך "למלא" את מבני הנתונים. מודלי שפה גדולים מודרניים מאומנים ליצור מידע מובנה וספריות העבודה איתם עוטפות את ההתנהגות הזו כך שאנחנו צריכים רק "לבקש" לקבל חזרה מידע בתור מחלקת pydantic וזה מה שהמודל יחזיר. בשביל להשתמש בטיפוס החזרה בתוך סוכן הרעיונות לפוסטים שלי אני כותב את הסוכן באופן הבא:
    market_research_agent = Agent(
        name="MarketResearcher",
        model=LitellmModel(model="github/gpt-4.1", api_key=os.environ["GITHUB_TOKEN"]),
        output_type=list[BlogPostIdea],
        instructions="""
        You are a market researcher and your job is to suggest cool ideas for blog posts.
        I will send you ideas and you will help me turn them into engaging posts,
        Or I will send you topics and you will help to focus me on the best viral ideas in these niches.
        """
    )
בתוך אוביקט האיתחול אני מעביר בפרמטר output_type את טיפוס הנתונים שאני רוצה לקבל חזרה. זה מספיק. עכשיו כשאני אקרא ל Runner.run אני אקבל בחזרה משהו שמתאים לטיפוס שהגדרתי. קוד התוכנית אנחנו מוכנים עכשיו לראות את קוד התוכנית המלא:
import asyncio
from agents import Agent, Runner
from agents.extensions.models.litellm_model import LitellmModel
import os
from pydantic import BaseModel, Field
import random
class BlogPostIdea(BaseModel):
    title: str = Field(..., title="Title", description="The title of the blog post"),
    main_concepts: list[str] = Field(..., title="Main Concepts", description="Main concepts for the post")

class BlogPost(BaseModel):

ToCode
1 419
בשביל להריץ את הקוד עלינו להתקין את החבילות וגם לקבל מפתח API ל OpenAI. את החבילות אני מתקין עם:
$ pip install "openai-agents" "openai-agents[litellm]"
ובדרך כלל נרצה לבצע את ההתקנה בתוך סביבה וירטואלית. בדוגמת הקוד שלי השתמשתי במודל של גיטהאב, או יותר נכון במודל של OpenAI וניגשתי אליו דרך מערכת המודלים של גיטהאב. עשיתי זאת כדי לקבל גישה בחינם למודל לצורך בדיקות. בעצם OpenAI דורשים תשלום על שימוש במודל כבר מהטוקן הראשון ולכן שימוש ישיר במודל דרך ה API שלהם מצריך יצירת API Key והזנת פרטי אשראי באתר של OpenAI. אנחנו פה עדיין בשלב הלימוד ולכן עדיף להתנסות בלי לשלם. גיטהאב מוכנים לשלם על הטוקנים שלכם בתקופת הלימוד עם הגבלה של כמה עשרות בקשות ביום. בשביל להריץ את הקוד דרכם נצטרך להוציא מפתח גישה דרך הקישור הזה: https://github.com/settings/personal-access-tokens לוחצים על הכפתור Generate New Token ומקפידים לסמן גישת קריאה ל Github Models. את הטוקן שקיבלתם מגדירים בתור משתנה סביבה GITHUB_TOKEN בעת הרצת תוכנית הפייתון. קריאת ה API הראשונה שלי ב TypeScript אם צד השרת שלכם כתוב ב TypeScript או JavaScript תשמחו לשמוע שיש מימוש מקביל לספריית ה OpenAI Agents SDK בטייפסקריפט. גם מימוש זה מאפשר להשתמש גם במודלים אחרים אבל לצערי נכון לכתיבת הפוסט החיבור למודלים של גיטהאב עדיין לא עובד. כאן אפשר לראות רשימה של כל ה Providers שכן אפשר לשלב: https://ai-sdk.dev/providers/ai-sdk-providers מאחר ולא היה לי מודל חינם לניסויים לקחתי את המודל בתשלום של OpenAI וכתבתי את התוכנית הבאה:
import { Agent, run } from 'npm:@openai/agents';

const agent = new Agent({
  name: 'History Tutor',
  model: 'gpt-4.1-nano',
  instructions:
    'You provide assistance with historical queries. Explain important events and context clearly.',
});

const result = await run(agent, 'When did sharks first appear?');

console.log(result.finalOutput);
בשביל להריץ אותה הגדרתי במשתנה הסביבה OPENAI_API_KEY את מפתח הגישה של OpenAI שהוצאתי (בתשלום). אתם יכולים גם לקבל אחד בקישור הזה: https://platform.openai.com/api-keys אחרי שתגדירו תוכלו להריץ את קוד הטייפסקריפט עם:
$ deno run -A main.ts
עכשיו אתם רוצים להמשיך לחקור את תוכניות הדוגמה? הנה שני כיוונים: 1. התבוננו במידע שפקודת run מחזירה - מה אתם יכולים ללמוד על הבקשה? 2. חברו את הסוכן למודלים אחרים ושימו לב לתשובות השונות שכל מודל מחזיר.

ToCode
1 419
יום 2 - קריאת API ראשונה למודל שפה גדול הבסיס של כל מערכת אג'נטית היא היכולת לפנות למודל שפה גדול ולקבל תשובה או יותר נכון "השלמה". בפוסט זה נראה איך לפנות למודל שפה גדול דרך הספריה openai-agents-sdk. ספריות גישה למודלים לפני שניגש לקוד צריך לשאול - למה דרך ספריה? ואם כבר ספריה למה דווקא ספריה מבית היוצר של OpenAI? כל חברות ה AI מציעות את שירותיהן בתשלום דרך ממשק REST API. אפשר לגשת אליהן מכל לקוח שמדבר HTTP. בנוסף יש אינספור מוצרי קוד פתוח שעוטפים את הגישה לאותם הממשקים. הסיבה שלא נרצה להשתמש בממשק ה REST API הבסיסי כדי לדבר עם ספקי המודלים שלנו היא שכל ספק בנה ממשק קצת אחרת. למשל בשביל לקבל השלמה מ OpenAI צריך לפנות לנתיב:
https://api.openai.com/v1/chat/completions
אבל בשביל לקבל השלמה ממודל של אנטרופיק צריך לפנות לנתיב:
https://api.anthropic.com/v1/messages
כשפונים ל OpenAI אנחנו שולחים מערך של הודעות שלכל הודעה יש role ו content, אבל אם נפנה ל Gemini נצטרך להעביר מערך של parts כשלכל part יש text. הרעיון עצמו אצל כל הספקים זהה אבל המימושים השונים גורמים לזה שאם נעבוד עם ממשק ה REST API נצטרך לבצע שינויים משמעותיים בקוד כל פעם שנרצה להחליף ספק או מודל. ספריות הגישה בקוד פתוח מאפשרות לכתוב פעם אחת את קוד ה"שאלה" והספריה כבר מתאימה את הקוד לכל מודל שנבחר. לדוגמה הספריה LiteLLM מאפשרת לי לכתוב שורת פייתון אחת כדי לקבל השלמת טקסט:
response = completion(
  model="openai/gpt-4o",
  messages=[{ "content": "Hello, how are you?","role": "user"}]
)
וכשרוצים לעבוד עם מודל אחר פשוט משנים את המודל. הספריה כבר דואגת להעביר את הפרמטרים ולבחור את ה URL בצורה שמתאימה לספק ה AI. הרמה הבאה של אבסטרקציה היא ספריות שלא רק מאפשרות גישה למודלים אלא גם מכתיבות את מבנה התוכנית ומספקות מימושים לתבניות נפוצות בכתיבת מערכות משולבות AI. ספריות כאלה כוללות: 1. LangGraph - ספריית פייתון שעוזרת לבנות תוכנית משולבת AI במבנה של גרף. 2. CrewAI - ספריית פייתון שמאפשרת לחשוב על התוכנית שלנו בתור צוות של סוכני AI שמדברים ביניהם. 3. Vercel AI SDK - ספריית עבודה עם סוכני AI שגם מטפלת בחיבור לריאקט ועוזרת בבניית ממשק משתמש לבוטים. 4. Autogen - פריימוורק לפיתוח ארכיטקטורה מרובת סוכנים מבית מייקרוסופט ויש עוד המון וכל הזמן יוצאות ספריות חדשות. האמת היא שתחום פיתוח מערכות אג'נטיות הוא עדיין מאוד צעיר והתעשייה מחפשת רעיונות, כלים וספריות. בסידרת פוסטים זו אני אראה את כל הדוגמאות עם הספריה OpenAI Agents SDK. בחרתי בספריה זו מהסיבות הבאות: 1. היא נכתבה על ידי OpenAI ומשלבת הרבה תבניות טובות בצורה פשוטה לשימוש. 2. היא כתובה גם בטייפסקריפט וגם ב Python ומאפשרת פיתוח מערכות בצד שרת וגם בצד לקוח. 3. היא לא עמוסה באבסטרקציות אלא כוללת יחסית מעט מבנים בסיסיים. אל דאגה, למרות שהספריה מבית OpenAI אפשר להשתמש בה כדי לדבר עם מודלים של כל החברות. קריאת ה AI הראשונה שלי בפייתון נכתוב את הקוד הבא בתוך תוכנית פייתון:
import asyncio
from agents import Agent, Runner
from agents.extensions.models.litellm_model import LitellmModel
import os

async def main():
    agent = Agent(
        name="Assistant",
        model=LitellmModel(model="github/gpt-4.1", api_key=os.environ["GITHUB_TOKEN"]),
        instructions="You only respond in haikus.",
    )

    result = await Runner.run(agent, "Tell me about recursion in programming.")
    print(result.final_output)

if __name__ == "__main__":
    asyncio.run(main())
הקוד די מסביר את עצמו ובואו נקרא אותו שורה אחר שורה: 1. מתחילים ביבוא הספריות. 2. ממשיכים להגדיר את הפונקציה הראשית main בתור פונקציית async. 3. הפונקציה מתחילה בהגדרה של הסוכן - סוכן הוא אבסטרקציה שמייצגת קריאה אחת או מספר קריאות ל AI כדי לבצע משימה. מאפיין instructions של הסוכן כולל טקסט הוראות הפעלה לאותו סוכן, המודל קובע מה המודל בו משתמשים ו name הוא שם הסוכן. 4. אחרי שיש לנו סוכן נוכל להריץ אותו עם פקודת Runner.run. הפקודה מקבלת את הסוכן ו prompt ויוצאת לדרך לבצע את המשימה. היא מחזירה את התוצאה יחד עם metadata לגבי מה שקרה. 5. בסיום ה main אני מדפיס את הפלט. הרצת התוכנית

ToCode
1 419
יום 1 - פיתוח מערכות אג'נטיות הי חברים, היום אנחנו מתחילים בסדרת 21 ימים על פיתוח מערכות אינטרנט מבוססות סוכנים. כבר היום מערכות אינטרנט רבות מוסיפות ממשק שיחה מבוסס AI בתור ממשק מלווה ל GUI הקלאסי. הדוגמאות בסידרה זו מבוססות על ממשקים כאלה שבניתי ללקוחות ולמערכות שלי ואני מקווה שהן יעזרו גם לכם. קוראים וותיקים זוכרים שאני מפרסם סדרות כאלה כשאני יוצא לחופשות וגם הפעם זה המצב - הבלוג יחזור למתכונתו הרגילה בסוף הסדרה בעוד 21 ימים. מה זה מערכת אג'נטית חברות ה AI הבינו מהר מאוד שהן יושבות על מכרה זהב. שתוך מעט מאוד זמן כל מוצר באינטרנט יצטרך לשלב יכולת בינה מלאכותית יוצרת ושיש פה הזדמנות פז להמציא תשתית חדשה ולהרוויח ממנה. ואכן כש ChatGPT יצא אחד הפיצ'רים הראשונים שקיבלנו היה API, וכמוהו גם כל אחד מהמתחרים. במקביל יש לנו מודלים בקוד פתוח כמו Llama או Deepseek שמאפשרים לכל אחד להתקין אותם על מחשב ולספק "שירותי AI" ללקוחות. דיגיטל אושן למשל עושים את זה ומאפשרים לכם בלחיצת כפתור להתחבר למודל. עבור סידרת הפוסטים שלנו אני אגדיר מערכת אג'ינטית בתור מערכת שמשתמשת באיזשהו API של ספק בינה מלאכותית יוצרת. זה יכול להיות מודל קוד פתוח שאתם מריצים על מחשב בסלון, מודל קוד פתוח שאתם מריצים בענן או שירות API של ספקי ה AI הגדולים. עוד דבר שעובד לטובתנו הוא שממשקי העבודה מול כל ספקי ה AI מאוד דומים אחד לשני: ברמה הבסיסית כולם מקבלים רצף של הודעות ויודעים לייצר הודעה חדשה לשיחה, וכולם יודעים לענות ב Streaming כך שאנחנו יכולים לחבר אותם ישירות לדף אינטרנט. וכן לספקים השונים יש יכולות שונות לדוגמה רבים מהם יודעים לייצר תמונות, חלקם יודעים לחפש באינטרנט, חלקם יודעים להריץ קוד או לייצר תמונות ועוד יכולות שונות ומגוונות. כשאנחנו בונים מערכת משולבת AI (או מערכת אג'נטית) צריך לזכור שמדובר בעולם תוכן מאוד חדש. כל הזמן יש ספריות חדשות וכלים חדשים לעבוד עם אותם ספקי AI וככל שאנשים בונים יותר מערכות משולבות AI מתגלות תבניות חדשות. בסידרה זו אני אעבוד עם ספריית Open AI Agents SDK שזמינה גם לפייתון וגם ל TypeScript. אלה שני הדברים המרכזיים שאנחנו הולכים לבנות: תהליך עבודה משולב AI סוג אחד של מערכות משולבות AI הוא תהליכי עבודה (workflow). תהליך עבודה הוא רצף של פעולות שיש לו סדר קבוע, כמו סקריפט, אבל שחלק מהשלבים בו דורשים השלמת מידע מספק AI. להלן מספר דוגמאות לתהליכי עבודה: 1. סיכום דף אינטרנט - הסקריפט עולה, מושך מהאינטרנט את הדף לקובץ HTML, אולי מוחק את תגיות ה HTML או מתרגם ל Markdown כדי של AI יהיה יותר קל לעבוד עם התוכן ואז שולח את הטקסט ל AI כדי שיסכם אותו. 2. פרסום פוסט לבלוג מרובה שפות - בן אדם כותב פוסט ואז מתחיל סקריפט ששולח את הפוסט ל AI כדי לתרגם אותו. אפשר לרוץ בלולאה או במקביל ולשלוח את הפוסט מספר פעמים לספק AI כל פעם כדי לתרגם לשפה אחרת. בסוף הסקריפט מעלה את כל התרגומים לאתר. 3. זיהוי בעיות אבטחה בפרויקט - סקריפט מושך את קוד הפרויקט ושולח את כל הקוד או קובץ אחרי קובץ ל AI כדי לחפש בעיות אבטחה. בסוף הסקריפט אוסף את כל התוצאות ונעזר ב AI כדי לנסח מייל סיכום שיישלח למפתחים. סוכן עצמאי סוג שני של מערכת אג'נטית הוא סוכן עצמאי. לסוכן כזה יש מטרה והוא מנהל אינטרקציה עם משתמשים כדי לבצע את המטרה. הנה מספר דוגמאות לסוכנים: 1. סוכן שממלא סקר - במקום לשלוח Google Form אני יכול להקים סוכן שיפנה למשתמש, ישאל שאלות ויכתוב בעצמו את התשובות לטופס. 2. סוכן שכותב פוסטים - הסוכן יכול לחפש באינטרנט נושאים מעניינים ולהציג למשתמש את הבחירה, אחרי זה הסוכן ינסח טיוטה ובעזרת דיאלוג עם המשתמש ישפר את הטיוטה עד שהיא תהפוך לפוסט שאפשר לפרסם. 3. סוכן מורה לפייתון - הסוכן ישלח למשתמש הסבר ואז מספר תרגילים, המשתמש יענה על התרגילים ואז הסוכן ייתן משוב ותרגילי המשך בהתבסס על התשובות של המשתמש. בכל סוג מערכת יהיו לנו אתגרים אחרים ולכל סוג מערכת יש את השימושים שלה. רוב הזמן שילוב AI במוצר דורש שילוב AI גם בתהליכי העבודה וגם בצורה אינטרקטיבית באמצעות יצירת סוכנים ולכן בעולם האמיתי נבנה מערכות משני הסוגים. מחר נתקין את הספריה ונבצע קריאות API ראשונות. בינתיים אתם מוזמנים לקפוץ ל AI החביב עליכם ובעזרתו לחשוב על עוד דוגמאות לסוכנים ותהליכי עבודה שתרצו לבנות.

ToCode
1 419
כשהבדיקות נכשלות כשבדיקה אחת נכשלת אפשר להסתכל ולתקן. כש 5 בדיקות נכשלות אפשר להסתכל ולתקן אבל זה יכול לקחת זמן, אז דוחים את זה לאחרי הגירסה. כש 15 בדיקות נכשלות כבר צריך להקצות לתיקון זמן בגאנט ולפתוח כרטיס בג'ירה. לפעמים מישהו מגיע לזה אבל כמעט תמיד יהיה משהו חשוב יותר לעשות. כש 50 בדיקות נכשלות הדרך היחידה להתקדם היא למחוק את כל הבדיקות ולהתחיל מחדש. למעשה המצב הבעייתי הוא כל מה שקורה בין 5 ל 50 בדיקות. זה הכשלונות שמצטברים בלי שאף אחד מטפל בהם ועם הבנה מלאה שזה עדיין לא מספיק גרוע בשביל להתחיל מחדש אבל מתישהו זה כן יהיה. כמו אוכל שנשאר במקרר יותר מדי ועדיין לא העלה עובש אז לא נעים לזרוק אבל גם אי אפשר לאכול. אבל הדבר המקצועי לעשות - גם באוכל, גם בקוד וגם בבדיקות הוא לזרוק מוקדם. אוכל שעוד לא העלה עובש לא יהיה טעים שבוע הבא, ובינתיים הוא רק תופס מקום במקרר. סט בדיקות עם 20 כשלונות לא יתחיל לעבור מעצמו ובינתיים הכשלונות הקבועים רק מסתירים את הכשלונות האמיתיים.

ToCode
1 419
# Advance each iterator to its starting position
    for i, it in enumerate(iterators):
        # The \deque\ trick is a fast way to advance an iterator
        # by 'i' steps without storing the results.
        deque(itertools.islice(it, i), maxlen=0)

    # Zip the advanced iterators together to create the sliding window
    # of size \size\ and stride \1\.
    windowed_iterator = zip(*iterators)

    # If stride is 1, we're done. Otherwise, apply the stride
    # by taking every Nth item from the windowed iterator.
    if stride == 1:
        return windowed_iterator
    else:
        return itertools.islice(windowed_iterator, None, None, stride)
הקוד של ג'מיני מטפל טוב יותר במקרי הקצה ואולי גם יותר יעיל. החיסרון שלו הוא שהוא מושך פריטים מה iterable גם לפני שהקוד החיצוני ביקש אותם. אגב כששאלתי אותו על זה ג'מיני מיהר להתגונן ולהסביר שב 99% מהמקרים זו לא בעיה אני לא בטוח שאני מסכים איתו. יש לכם רעיון נוסף איך לממש חלון גולש בפייתון ורוצים לשתף? אפשר ורצוי להדביק בתגובות או בטלגרם.

ToCode
1 419
תרגיל פייתון: הרחבת pairwise עם גודל וגודל צעד הפונקציה itertools.pairwise בפייתון לוקחת רשימה ומחזירה רשימה של זוגות חופפים מתוך הקלט המקורי, לדוגמה:
>>> import itertools
>>> list(itertools.pairwise(range(100)))
[(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9), (9, 10), (10, 11), (11, 12), (12, 13), (13, 14), (14, 15), (15, 16), (16, 17), (17, 18), (18, 19), (19, 20), (20, 21), (21, 22), (22, 23), (23, 24), (24, 25), (25, 26), (26, 27), (27, 28), (28, 29), (29, 30), (30, 31), (31, 32), (32, 33), (33, 34), (34, 35), (35, 36), (36, 37), (37, 38), (38, 39), (39, 40), (40, 41), (41, 42), (42, 43), (43, 44), (44, 45), (45, 46), (46, 47), (47, 48), (48, 49), (49, 50), (50, 51), (51, 52), (52, 53), (53, 54), (54, 55), (55, 56), (56, 57), (57, 58), (58, 59), (59, 60), (60, 61), (61, 62), (62, 63), (63, 64), (64, 65), (65, 66), (66, 67), (67, 68), (68, 69), (69, 70), (70, 71), (71, 72), (72, 73), (73, 74), (74, 75), (75, 76), (76, 77), (77, 78), (78, 79), (79, 80), (80, 81), (81, 82), (82, 83), (83, 84), (84, 85), (85, 86), (86, 87), (87, 88), (88, 89), (89, 90), (90, 91), (91, 92), (92, 93), (93, 94), (94, 95), (95, 96), (96, 97), (97, 98), (98, 99)]
בואו נרחיב אותה באמצעות שני פרמטרים: 1. פרמטר size קובע את גודל ה״זוג״ (וכן כל מספר גדול מ-2 כבר לא יהיה זוג). 2. פרמטר stride קובע את גודל הצעד כלומר החפיפה בין הזוגות. לדוגמה ארצה להיות מסוגל להפעיל את הפונקציה כדי לקבל את כל השלשות החופפות בטווח המספרים עד 100, או לקבל קבוצות של 5 מספרים בהפרש של 3 אחת מהשנייה. פיתרון כדי לפתור את התרגיל אני מתחיל עם הקוד של pairwise עצמה מתוך דף התיעוד:
def pairwise(iterable):
    # pairwise('ABCDEFG') → AB BC CD DE EF FG

    iterator = iter(iterable)
    a = next(iterator, None)

    for b in iterator:
        yield a, b
        a = b
בעצם הפונקציה מבצעת איטרציה כפולה - לוקחים את האיבר הראשון למשתנה a, ואז מתחילים לרוץ על האיטרטור עם המשתנה b שעכשיו מקבל את האיבר השני (כי הראשון כבר נלקח ל a). מחזירים את הזוג וממשיכים לסיבוב נוסף, הפעם a מקבל את הערך של b כלומר את האיבר השני ו b מקבל את הדבר הבא מהאיטרטור שזה כבר האיבר השלישי. ומכאן ההרחבה למקרה הכללי ברורה - במקום לקחת רק איבר אחד קדימה עם next נרצה לקחת בלוק שלם קדימה עם itertools.islice. זה הקוד:
import itertools

def each_cons(iterable, size=2, stride=1):
    iterator = iter(iterable)
    chunk = tuple(itertools.islice(iterator, size))
    for b in iterator:
        yield chunk
        chunk = chunk[stride:] + (b,) + tuple(itertools.islice(iterator, stride - 1))
    yield chunk
נשים לב: 1. בגלל העבודה עם איטרטורים אין איטרציה כפולה, כלומר הקוד לא צריך לטעון את כל המידע לזיכרון לפני שמייצר את הזוגות (או שלשות, או רביעיות). 2. הכתיב (b,) זאת הדרך של פייתון לייצר tuple של איבר אחד. הקוד הזה עוזר להמחיש את הפיתרון אבל הוא לא מושלם. קל לראות שכש stride גדול מ size מקבלים chunk-ים גדולים מדי (בגלל אופן היצירה של chunk), אין וולידציה של פרמטרים וכל חיבורי ה tuples האלה יכולים לפגוע בביצועים. נתתי לג'מיני לשפר את המימוש וקיבלתי את הקוד הזה:
import itertools
from collections import deque

def each_cons(iterable, size=2, stride=1):
    """
    Yields overlapping tuples of a given size and stride.

    Like a sliding window, but the window can jump.

    Args:
        iterable: The iterable to process.
        size: The size of each chunk (window).
        stride: The number of items to advance between chunks.

    Yields:
        A tuple of length \size\ for each step.

    Examples:
        >>> list(each_cons([1, 2, 3, 4, 5], size=3, stride=1))
        [(1, 2, 3), (2, 3, 4), (3, 4, 5)]

        >>> list(each_cons([1, 2, 3, 4, 5, 6], size=3, stride=2))
        [(1, 2, 3), (3, 4, 5)]

        >>> list(each_cons([1, 2], size=3))
        []
    """
    if size < 1 or stride < 1:
        raise ValueError("size and stride must be positive integers")

    # Create 'size' independent iterators
    iterators = itertools.tee(iterable, size)

ToCode
1 419
בגישה הפונקציונאלית הרכבה (קומפוזיציה) היא הרכבה של פונקציות, והיא מתבססת על זה שיש לנו הרבה פונקציות קטנות ועצמאיות ואנחנו יכולים לפרק ולהרכיב אותן כדי ליצור מבנים חדשים בקלות. הבעיה עם ספריות פרונטאנד פונקציונאליות היא שהפונקציות גדולות מדי. אין לי איך לקחת רק "חלקים" מקומפוננטה ולבנות מהם קומפוננטות חדשות, או לפחות לרוב זה מסורבל מדי. פונקציות Custom Hooks מציעות פיתרון חלקי בלבד ובאופן כללי קומפוננטות שאנחנו מקבלים מאנשים אחרים צריכות להיכתב מראש בצורה שתאפשר הרחבה שלהן מבחוץ (למשל על ידי שימוש נרחב ב Custom Hooks).

ToCode
1 419
שינוי קומפוננטות מבחוץ רוב הספריות הפופולריות לפיתוח צד לקוח לא תומכות בהרחבה של קומפוננטות מבחוץ וזה חבל. נדמיין שקיבלתי קומפוננטת ריאקט של מונה לחיצות:
export default function Counter() {
    const [count, setCount] = useState(0);
    
    return (
        <div>
            <p>Count = {count}</p>
            <button onClick={() => setCount(c => c + 1)}>+1</button>
        </div>
    );
}
אני רוצה להוסיף לו כפתור איפוס, אבל בלי לשנות את הקובץ המקורי של Counter. אפילו אם אני מוכן לשנות את הקריאות ל Counter מבחוץ כלומר לעטוף אותו באיזה CounterWithReset אין לי איך לעטוף את הקומפוננטה ולשנות את ה Virtual DOM שהיא מחזירה. זה פשוט לא בכללים. בואו נראה שתי דוגמאות לספריות שכן מאפשרות מנגנון כזה ומה הופך אותו לאפשרי. הרחבת Counter ב lit דוגמה ראשונה היא lit שבונה קומפוננטות באמצעות Web Components. ב lit קומפוננטת Counter נראית כך:
import { LitElement, html } from 'lit';

export class MyCounter extends LitElement {
  static properties = {
    count: { type: Number }
  };

  constructor() {
    super();
    this.count = 0;
  }

  render() {
    return html\
      <button @click=${() => this.count--}>-</button>
      <span>${this.count}</span>
      <button @click=${() => this.count++}>+</button>
    \;
  }
}

customElements.define('my-counter', MyCounter);
ההבדל בין זה לריאקט הוא שליט השתמשה בקלאס במקום בפונקציה (קצת כמו קלאסים בריאקט שעשינו פעם). היתרון בקלאס הוא שאפשר להרחיב אותו מבחוץ ולכן אני יכול מקובץ אחר לטעון את הקומפוננטה, לשנות בה חלקים ולייצא מחדש:
import { html } from 'lit';
import { MyCounter } from "./my-counter";

// Inject a reset method
MyCounter.prototype.reset = function () {
  this.count = 0;
  this.requestUpdate(); // tell Lit to re-render
};

// Patch render to add a Reset button
const origRender = MyCounter.prototype.render;
MyCounter.prototype.render = function () {
  return html\
    ${origRender.call(this)}
    <button @click=${() => this.reset()}>Reset</button>
  \;
};

export default MyCounter;
הרינדור של הקומפוננטה הפנימית והרחבה עם כפתור חדש קצת מזכיר שימוש ב Higher Order Component בריאקט, אבל יש פה הבדל משמעותי של גישה לסטייט. עם HoC הסטייט של הקומפוננטה הפנימית היה מוגן ואפשר היה להגיע אליו רק מתוך קוד שרץ באותה קומפוננטה. בעבודה עם קלאסים ושינוי פרוטוטייפים אפשר להגיע גם מבחוץ לאותו משתנה count ששמור בסטייט. הרחבת Counter ב Stimulus גישה שנייה להרחבה היא של ריילס וספריית ה JavaScript שלה, סטימולוס. ה Counter בסטימולוס מורכב מקובץ JavaScript ומקובץ html.erb. זו התבנית:
<div 
  data-controller="counter" 
  data-counter-count-value="0"
>
  <button data-action="counter#decrement">-</button>
  <span data-counter-target="output">0</span>
  <button data-action="counter#increment">+</button>
</div>
וזה קובץ ה JavaScript:
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["output"]
  static values = { count: Number }

  connect() {
    this.update()
  }

  increment() {
    this.countValue++
    this.update()
  }

  decrement() {
    this.countValue--
    this.update()
  }

  update() {
    this.outputTarget.textContent = this.countValue
  }
}
רוצים להוסיף איפוס? אין בעיה אפשר לעדכן את ה Controller מבחוץ ולהוסיף פונקציה:
import CounterController from "./counter_controller"

CounterController.prototype.reset = function () {
  this.countValue = 0;
  this.update()
}
את התבנית אפשר לשנות או לרנדר אותה מקובץ טמפלייט אחר.

ToCode
1 419
השאלה הלא נכונה של Vibe Coding "זה עובד?" כל הסיפור של Vibe Coding מבוסס על השאלה הזאת. אני מתאר ל AI את המערכת שאני רוצה לבנות וממשיך בדיאלוג עד שאני מגיע למצב שהפרויקט עובד. במקרה הטוב זה אפילו מצליח. וזה בכלל לא משנה. הנה כמה שאלות שתמיד היו חשובות וקשה לראות למה עכשיו פתאום לא אכפת לנו מהן יותר: 1. כמה קל יהיה לשנות את הפרויקט כשהדרישות ישתנו? 2. כמה משאבים זה צריך? 3. כשזה יישבר, האם אדע לתקן? 4. כשימציאו חומרה חדשה, מערכת הפעלה חדשה או גירסאות חדשות לספריות, כמה קל יהיה להתאים את המערכת לתשתית החדשה? 5. אפשר לשבור את זה בטעות? 6. אפשר לשבור את זה בכוונה? 7. מה עדיין לא עובד? 8. איזה דרישות חדשות עשויות להיות לי שלא יתאימו לארכיטקטורה שנבחרה ויכריחו אותי לבנות מערכת חדשה? כשאני נותן ל AI לכתוב קוד לא מעניין אותי אם הקוד שהוא כתב עובד. אם זה נכון ולא עובד אני ממילא אוכל בקלות לתקן את זה, ואם זה לא נכון ועובד אין לי מה לעשות עם זה. כן יש בעיות שאפשר לפתור עם מערכת קטנה שלא תשתנה וברור שכלי AI יכולים היום לבנות מערכת קטנה כזאת שפעם עלתה אלפי או עשרות אלפי דולרים ולבנות אותה בכמה ימים. זה מדהים. אבל לא על זה רובנו עובדים ביום יום.