יום 16 - העברת מידע לכלים באמצעות Context

15/10/2025

כשסוכן מפעיל כלי הוא מעביר ערכים לפרמטרים של אותו כלי. אני אומנם יכול לבקש מהסוכן להעביר ערכים מסוימים אבל האמת שהשליטה שלי באיזה ערכים יעברו מוגבלת. קונטקסט הוא מנגנון נוסף להעברת מידע לכלים שמאפשר שליטה מדויקת יותר במידע שעובר וגם סינכרון מידע בין כלים.

1. מתי נצטרך להשתמש בקונטקסט

המצבים המרכזיים בהם נרצה להשתמש בקונטקסט הם:

  1. כשאנחנו רוצים להעביר לכלי מידע בלי להעביר את המידע הזה ל AI.

  2. כשאנחנו רוצים שליטה מדויקת על המידע שעובר לכלי.

  3. כשאנחנו רוצים לסנכרן מידע בין כלים.

בואו נראה דוגמה לכל אחד מהמצבים ואיך לממש אותה עם קונטקסט ב OpenAI Agents SDK.

2. העברת מידע סודי לכלי

באחת מתוכניות הדוגמה שראינו כאן בסידרה כתבתי כלי שבודק מה מזג האוויר דרך Open Weather Map. זה היה הקוד שלו:

@function_tool
async def fetch_weather(location: Location) -> str:
    base_url = "https://api.openweathermap.org/data/2.5/weather"
    params = {
        "lat": location["lat"],
        "lon": location["long"],
        "appid": weather_api_key,
        "units": "metric"
    }

    # Run the synchronous requests call in a thread pool
    loop = asyncio.get_event_loop()
    response = await loop.run_in_executor(None, requests.get, base_url, params)
    response.raise_for_status()

    data = response.json()
    weather_desc = data["weather"][0]["description"]
    temp = data["main"]["temp"]
    feels_like = data["main"]["feels_like"]
    humidity = data["main"]["humidity"]

    return f"Weather: {weather_desc.title()}, Temperature: {temp}°C (feels like {feels_like}°C), Humidity: {humidity}%"

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

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

בשביל לעבור להשתמש בקונטקסט אני מגדיר קלאס שמתאר את מבנה הקונטקסט:

class AssistantContext(BaseModel):
    weather_api_url: str
    weather_api_key: str

לאחר מכן אני מעדכן את הכלי ומעביר אוביקט שמכיל את הקונטקסט בתור פרמטר ראשון לכלי. אני משתמש במפתח context של אותו אוביקט עוטף כדי להגיע לקונטקסט שלי:

@function_tool
async def fetch_weather(wrapper: RunContextWrapper[AssistantContext], location: Location) -> str:
    base_url = wrapper.context.weather_api_url
    params = {
        "lat": location["lat"],
        "lon": location["long"],
        "appid": wrapper.context.weather_api_key,
        "units": "metric"
    }

    # Run the synchronous requests call in a thread pool
    loop = asyncio.get_event_loop()
    response = await loop.run_in_executor(None, requests.get, base_url, params)
    response.raise_for_status()

    data = response.json()
    weather_desc = data["weather"][0]["description"]
    temp = data["main"]["temp"]
    feels_like = data["main"]["feels_like"]
    humidity = data["main"]["humidity"]

    return f"Weather: {weather_desc.title()}, Temperature: {temp}°C (feels like {feels_like}°C), Humidity: {humidity}%"

לסיום בהפעלת הסוכן אני מייצר אוביקט קונטקסט ומעביר אותו ל Runner:

async def main():
    session = SQLiteSession("weather", "weather.db")
    ctx = AssistantContext(weather_api_key=os.getenv("OPENWEATHER_API_KEY"), weather_api_url="https://api.openweathermap.org/data/2.5/weather")
    result = await Runner.run(agent, "I'm planning a trip to Israel, what is the weather in Tel Aviv, Jerusalem, Haifa and Eilat today?", session=session, context=ctx)
    print(result.final_output)

הקוד שוב עובד אבל עכשיו אני יכול לכתוב תוכנית בדיקה בה הכלי ישתמש ב URL ומפתח אחרים והשינוי היחיד יהיה ביצירת אוביקט הקונטקסט והעברתו ל Runner בתוכנית הבדיקה.

3. העברת מידע בין כלים

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

from optparse import Option

from typing_extensions import TypedDict
from agents import Agent, function_tool, Runner, SQLiteSession, RunContextWrapper, run_demo_loop
from agents.extensions.models.litellm_model import LitellmModel
from pydantic import BaseModel
from typing import Optional

import requests
import os
import asyncio

class UserContext(BaseModel):
    name: Optional[str]
    favorite_programming_language: Optional[str]

@function_tool
async def set_name(wrapper: RunContextWrapper[UserContext], name: str) -> str:
    """Save user name"""
    wrapper.context.name = name
    return f"User name set to {name}"

@function_tool
async def set_favorite_programming_language(wrapper: RunContextWrapper[UserContext], favorite_programming_language: str) -> str:
    wrapper.context.favorite_programming_language = favorite_programming_language
    return f"Favorite programming language set to {favorite_programming_language}"


agent = Agent(
    name="Assistant",
    model=LitellmModel(model="github/gpt-4.1", api_key=os.environ["GITHUB_TOKEN"]),
    instructions="You are a programming teacher and you want to welcome students to your class. Find out what their name and favorite programming languages are and save the information using the provided tools. Be gentle with the students and ask just one question at a time",
    tools=[set_name, set_favorite_programming_language],
)

async def main():
    session = SQLiteSession("info")
    ctx = UserContext(name=None, favorite_programming_language=None)

    next_message = "Start the conversation with the student."
    while True:
        result = await Runner.run(agent, next_message, session=session, context=ctx)
        print(result.final_output)
        if ctx.name is not None and ctx.favorite_programming_language is not None:
            break

        next_message = input()

    print(ctx)

if __name__ == "__main__":
    asyncio.run(main())

שימו לב איך שני הכלים מעדכנים את אותו אוביקט ובסוף הלולאה הראשית יכולה להדפיס את האוביקט.

4. נ.ב. למה לא משתנה גלובאלי?

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

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

  2. משתנים גלובאליים מייצרים תלות סמויה - בתוכנית גדולה לא תמיד קל לזהות איזה כלים תלויים באיזה משתנים.

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

  4. קל יותר להעביר ערכים אחרים לקונטקסט בסביבת בדיקות.

5. עכשיו אתם

שמתם לב שהמשתנה שעובר ל Tool נקרא wrapper והוא מסוג RunContextWrapper? נסו לגלות מה השדות האחרים של שמוגדרים על אוביקט זה וחישבו באיזה מצבים תרצו להשתמש גם בהם.