יום 3: תקציר חדשות יומי

15/08/2026

ראינו כבר איך ליצור שאלות הבנה מדף אינטרנט, אבל מה קורה אם יש כמה דפי אינטרנט? בדוגמה של היום נראה איך לתת לסוכן שלנו את החופש לחקור לבד כדי שיוכל להחליט אוטונומית מהם המאמרים הכי חשובים ולתקצר אותם עבורנו.

1. הפעלת כלים

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

וכל התיאוריה הזאת מגיעה מובנית בתוך הספריה Pydantic AI Agents בצורה נוחה לשימוש:

  1. אפשר להגדיר כל פונקציה בתור "כלי" והמודל יוכל להפעיל אותה.

  2. פידנטיק AI מגדירה מספר כלים "מובנים", שהם בעצם פונקציות של הספריה שנכתבו כבר בשבילכם.

  3. פידנטיק AI חושפים ממשק גישה לכלים המובנים של הספק.

הנה דוגמה פשוטה מתוך התיעוד של פידנטיק להגדרת כלים והעברתם לסוכן:

import random

from pydantic_ai import Agent, RunContext

agent = Agent(
  'google:gemini-3-flash-preview',
  deps_type=str,
  instructions=(
      "You're a dice game, you should roll the die and see if the number "
      "you get back matches the user's guess. If so, tell them they're a winner. "
      "Use the player's name in the response."
  ),
)


@agent.tool_plain
def roll_dice() -> str:
  """Roll a six-sided die and return the result."""
  return str(random.randint(1, 6))


@agent.tool
def get_player_name(ctx: RunContext[str]) -> str:
  """Get the player's name."""
  return ctx.deps


dice_result = agent.run_sync('My guess is 4', deps='Anne')
print(dice_result.output)
#> Congratulations Anne, you guessed correctly! You're a winner!

נתחיל עם מה שקל: הפונקציות roll_dice ו get_player_name מוגדרות בתור פונקציות פייתון פשוטות עם הדקורטור @agent.tool או @agent.tool_plain. ההבדל בין השניים הוא ש tool יכול לקבל פרמטרים מהתוכנית וגם מהמודל ו tool_plain יכול לקבל פרמטרים רק מהמודל.

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

2. מה אנחנו בונים

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

3. הצגת הכלים

שני הכלים בתוכנית מוגדרים בקובץ extractors.py. רציתי לנסות דרכים שונות לקבל מידע מהאינטרנט ולכן יצרתי שני כלים, אחד נקרא custom fetch והוא מקבל URL ומשתמש ב trafilatura כדי לקבל ממנו את הטקסט. כלי שני נקרא tavily fetch והוא משתמש בשירות רשת בשם tavily כדי לקחת את הטקסט מתוך URL.

בנוסף לכלים שלנו התוכנית מאפשרת גם שימוש בכלי ה fetch המובנה של Gemini, ובכלי ה fetch המובנה של Pydantic AI שירוץ מהמכונה שלי. כך נראית פונקציית יצירת הסוכן בקובץ digest.py

def build_agent(tool: str) -> Agent[None, Digest]:
    """Build the digest agent, wiring up the selected extraction backend."""
    provider = GoogleProvider(api_key=os.environ["GEMINI_API_KEY"])
    model = GoogleModel("gemini-3-flash-preview", provider=provider)

    tools: list[Tool] = []
    capabilities: list = []

    if tool == "tavily":
        tools.append(Tool(make_tavily_fetch()))
    elif tool == "custom":
        tools.append(Tool(make_custom_fetch()))
    elif tool == "web_fetch_tool":
        # Provider-adaptive: native fetch when available, local fallback otherwise.
        capabilities.append(WebFetch(local=True, native=False))
    elif tool == "native":
        # Native-only. WebFetch itself raises UserError if the provider has no
        # native web-fetch tool (e.g. Gemini) — no fallback, no custom guard.
        capabilities.append(WebFetch(local=False))
    else:
        raise ValueError(f"Unknown tool backend: {tool}")

    return Agent(
        model,
        output_type=Digest,
        tools=tools,
        capabilities=capabilities,
        instructions=INSTRUCTIONS,
    )

פרמטר tools מגדיר למודל רשימת כלים בהם יוכל להשתמש. פרמטר capabilities מגדיר כלים מובנים של הספריה או של ה API. הקוד בוחר את הכלים לפי הפרמטר tool שמגיע מבחוץ.

לסוכן הזה אנחנו שולחים את ההודעה הבאה:

PROGRAMMING_INDEX_SITES = [
    "https://reddit.com/r/programming",
    "https://news.ycombinator.com",
    "https://lobste.rs",
    "https://dev.to",
    "https://hashnode.com",
    "https://daringfireball.net",
    "https://arstechnica.com",
    "https://theverge.com/tech",
    "https://github.com/trending",
    "https://www.python.org/blogs/",
    "https://blog.rust-lang.org",
    "https://go.dev/blog",
    "https://nodejs.org/en/blog",
    "https://kubernetes.io/blog",
    "https://www.phoronix.com",
]

def build_digest(tool: str, max_articles: int) -> Digest:
    """Run the agent and return the produced digest."""
    agent = build_agent(tool)
    site_list = "\n".join(f"- {s}" for s in PROGRAMMING_INDEX_SITES)
    prompt = (
        f"Build today's programming digest with about {max_articles} articles.\n\n"
        f"Index pages you may browse:\n{site_list}"
    )
    return agent.run_sync(prompt).output

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

4. עכשיו אתם

  1. הריצו את הדוגמה אצלכם עם כל אחד מהכלים. שימו לב שתצטרכו מפתח API מ tavily. זה בחינם ונרשמים כאן Tavily

  2. נסו לאלף את המודל - הגדירו כלי "סיום" שמקבל את ה Digest ובודק שיש בו לפחות 7 מאמרים. אם יש פחות החזירו כשלון למודל ובקשו תקציר טוב יותר.

  3. נסו לשנות את הדינמיקה - במקום כלי "סיום" אחד הגדירו כלי "שמירת מאמר" ותנו למודל להפעיל אותו כמה פעמים שהוא רוצה בשביל לשמור מאמרים לתקציר היומי. האם יש הבדל במספר המאמרים? באיכות שלהם?