הנדסת הקשר (Context Engineering): המדריך המלא לבניית מערכות AI לייצור ב-2026

הנדסת הקשר (Context Engineering) היא מה שמחליף את הנדסת הפרומפטים ב-2026. למדו לבנות מערכות AI לייצור עם ניהול דינמי של חלון ההקשר, אסטרטגיות RAG, Memory Blocks, ו-caching סמנטי — עם דוגמאות קוד מלאות.

מבוא: מעבר להנדסת פרומפטים — עידן הנדסת ההקשר

אם עקבתם אחרי ההתפתחויות בתעשיית ה-AI ב-2024-2025, ודאי שמתם לב למונח שחזר שוב ושוב: "הנדסת פרומפטים" (Prompt Engineering). היה נראה שזה הכישור החם ביותר — משרות שהציעו 300 אלף דולר בשנה רק על כתיבת פרומפטים טובים. אבל ב-2026? הדברים השתנו. בצורה דרמטית.

לפי סקר של Fast Company ממאי 2025, תפקיד "מהנדס פרומפטים" כמשרה עצמאית כמעט נעלם לחלוטין. סקר של מיקרוסופט על 31,000 עובדים דירג את התפקיד במקום הלפני-אחרון ברשימת המשרות שחברות מתכננות להוסיף. אבל — וזה ה"אבל" החשוב — הכישור עצמו לא נעלם. הוא פשוט התפתח למשהו הרבה יותר מקיף: הנדסת הקשר (Context Engineering).

אז מה בדיוק קרה? למה "לכתוב פרומפט טוב" כבר לא מספיק? והכי חשוב — איך בונים מערכות AI מבוססות הנדסת הקשר שבאמת עובדות בייצור?

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

מהי הנדסת הקשר? וכמה היא באמת שונה מהנדסת פרומפטים?

הנדסת הקשר היא הדיסציפלינה של תכנון ובניית מערכות דינמיות שמספקות למודל שפה גדול (LLM) את כל המידע הנכון, בפורמט הנכון, בזמן הנכון — כדי שיוכל לבצע את המשימה בהצלחה. זה הרבה מעבר לכתיבת הוראה טקסטואלית טובה.

אנדריי קרפאתי (Andrej Karpathy), חוקר AI ומייסד שותף של OpenAI, הגדיר את זה בצורה שאני חושב שפשוט מסמרת: "האמנות והמדע העדינים של מילוי חלון ההקשר בדיוק במידע הנכון."

ההבדל בין שתי הגישות פשוט אבל קריטי:

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

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

לפי דו"ח State of Agent Engineering של LangChain לשנת 2025, 57% מהארגונים כבר מפעילים סוכני AI בייצור. אבל 32% מציינים את האיכות כמכשול המרכזי. ורוב הכשלים? הם בכלל לא כשלים של המודל — הם כשלים של הקשר. המודל קיבל מידע חסר, לא עדכני, או רועש, ובמקום לומר "אני לא יודע", הוא מילא את הפערים בניחושים. (מכירים את זה, נכון?)

טבלת השוואה מהירה

מימדהנדסת פרומפטיםהנדסת הקשר
היקףקלט-פלט בודדכל מה שהמודל "רואה" — זיכרון, היסטוריה, כלים, מסמכים
גישהניסוח הוראות ברורותתכנון ארכיטקטורת מידע שלמה
סקלביליותקורסת כשגדלים — יותר משתמשים = יותר מקרי קצהמתוכננת לגודל מהיום הראשון
דיבאגינגשינוי ניסוח וניחוש מה השתבשבדיקת כל חלון ההקשר, slot-ים של זיכרון, ותנועת טוקנים
כלים נדרשיםתיבת פרומפט בלבדמודולי זיכרון, מערכות RAG, שרשרת API-ים, תיאום backend
חזרתיותמשתנה — דורש התאמות ידניותמתוכננת לעקביות ושימוש חוזר

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

אנטומיה של חלון ההקשר: 6 השכבות

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

  1. System Prompt — ההוראות הבסיסיות שמגדירות את התפקיד, ההתנהגות, והכללים של המודל. זה החלק ה"קבוע" יחסית.
  2. היסטוריית שיחה — ההודעות הקודמות בשיחה הנוכחית. בגלל שמודלים הם stateless (לא זוכרים בין קריאות), צריך לשלוח את כל ההיסטוריה בכל פעם מחדש.
  3. מסמכים שנשלפו (RAG) — מידע רלוונטי שנמשך ממאגרי ידע, מסדי נתונים וקטוריים, או מקורות חיצוניים.
  4. זיכרון ארוך טווח — עובדות, העדפות, ותובנות שנצברו לאורך אינטראקציות קודמות.
  5. הגדרות כלים (Tool Definitions) — תיאורי ה-API-ים, הפונקציות, והכלים שהמודל יכול להפעיל.
  6. קלט המשתמש — השאלה או המשימה הנוכחית.

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

ארבע האסטרטגיות המרכזיות: כתוב, בחר, דחוס, בודד

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

1. כתוב (Write) — שמירת הקשר לאחסון חיצוני

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

from langgraph.graph import StateGraph, START, END
from typing import TypedDict, Annotated
import operator

class AgentState(TypedDict):
    """State that persists across agent steps"""
    messages: Annotated[list, operator.add]
    user_preferences: dict      # Long-term user data
    retrieved_docs: list        # RAG results
    tool_results: list          # Tool execution outputs
    context_summary: str        # Compressed history

def save_to_memory(state: AgentState) -> AgentState:
    """Extract and persist key facts from conversation"""
    messages = state["messages"]
    # Extract facts worth remembering
    facts = extract_facts(messages[-1])
    # Save to external vector store
    memory_store.add_documents(facts)
    return {"messages": [{"role": "system",
                          "content": f"Saved {len(facts)} facts to memory"}]}

graph = StateGraph(AgentState)
graph.add_node("save_memory", save_to_memory)

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

2. בחר (Select) — שליפת מידע רלוונטי בלבד

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

from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings

# Step 1: Chunk documents intelligently
text_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
    chunk_size=1500,    # Balance between precision and context
    chunk_overlap=100   # Preserve cross-chunk context
)
chunks = text_splitter.split_documents(raw_docs)

# Step 2: Index in vector store
embeddings = OpenAIEmbeddings(model="text-embedding-3-large")
vectorstore = FAISS.from_documents(chunks, embeddings)

# Step 3: Retrieve with relevance threshold
retriever = vectorstore.as_retriever(
    search_type="similarity_score_threshold",
    search_kwargs={
        "score_threshold": 0.75,  # Only high-relevance docs
        "k": 5                     # Max 5 chunks
    }
)

def select_context(state: AgentState) -> AgentState:
    """Retrieve only relevant documents for current query"""
    query = state["messages"][-1]["content"]
    docs = retriever.invoke(query)
    return {"retrieved_docs": docs}

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

3. דחוס (Compress) — כיווץ מידע לחיסכון בטוקנים

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

from langchain.memory import ConversationSummaryBufferMemory
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

# Keep recent messages verbatim, summarize older ones
memory = ConversationSummaryBufferMemory(
    llm=llm,
    max_token_limit=2000,    # Summarize when exceeding this
    return_messages=True
)

# Alternative: Middleware-based approach in LangGraph
def compress_context(state: AgentState) -> AgentState:
    """Summarize old messages to free up token budget"""
    messages = state["messages"]
    if count_tokens(messages) > 6000:
        old_messages = messages[:-4]  # Keep last 4 verbatim
        recent_messages = messages[-4:]
        summary = llm.invoke(
            f"Summarize this conversation concisely:\n"
            f"{format_messages(old_messages)}"
        )
        return {
            "messages": [
                {"role": "system", "content": f"Summary: {summary}"},
                *recent_messages
            ],
            "context_summary": summary.content
        }
    return state

יש שלוש גישות עיקריות לדחיסה, כל אחת עם ה-tradeoff שלה:

  • חיתוך (Truncation) — מהיר אבל מאבד מידע. פשוט שומרים את N ההודעות האחרונות ומקווים לטוב.
  • חלון נגלל (Sliding Window) — מאוזן יותר. שומרים את K ההודעות האחרונות ומוחקים את הישנות.
  • סיכום רקורסיבי — יקר (דורש קריאת LLM נוספת) אבל שומר על המשמעות. מסכמים הודעות ישנות לפרגרף אחד.

4. בודד (Isolate) — הפרדת הקשרים בין סוכנים

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

from langgraph.graph import StateGraph

# Each agent gets its own isolated context
class CodeReviewState(TypedDict):
    code_diff: str
    review_guidelines: str
    messages: list

class CustomerSupportState(TypedDict):
    customer_id: str
    ticket_history: list
    messages: list

# Supervisor delegates with filtered context
def route_to_agent(supervisor_state):
    task_type = classify_task(supervisor_state["messages"][-1])

    if task_type == "code_review":
        return CodeReviewState(
            code_diff=supervisor_state["current_diff"],
            review_guidelines=load_guidelines(),
            messages=[]  # Fresh context for code agent
        )
    elif task_type == "support":
        return CustomerSupportState(
            customer_id=supervisor_state["customer_id"],
            ticket_history=fetch_tickets(
                supervisor_state["customer_id"]
            ),
            messages=[]  # Isolated from code context
        )

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

ניהול חלון ההקשר בייצור: פה רוב המערכות נכשלות

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

העיקרון המנחה: פחות זה יותר (ברצינות)

מחקר של NoLiMa (benchmark להערכת הקשרים ארוכים) גילה ממצא שהפתיע הרבה אנשים בתעשייה: ב-32,000 טוקנים, 11 מתוך 12 מודלים שנבדקו ירדו מתחת ל-50% מהביצועים שלהם בהקשרים קצרים. כלומר, המטרה היא לא "לנצל את מקסימום חלון ההקשר", אלא "להשתמש במינימום ההקשר הנדרש לתשובה איכותית".

ויש עוד: מחקר של Levy, Jacoby ו-Goldberg מ-2024 מצא שביצועי ההסקה של LLM-ים מתחילים להידרדר כבר מסביב ל-3,000 טוקנים. זה הרבה מתחת למגבלות הטכניות שאנחנו רגילים לשמוע עליהן. ה-sweet spot למרבית המשימות? 150-300 מילים.

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

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

import tiktoken

def count_tokens(text: str, model: str = "gpt-4o") -> int:
    """Accurate token counting per model"""
    encoding = tiktoken.encoding_for_model(model)
    return len(encoding.encode(text))

def budget_context_window(
    system_prompt: str,
    conversation_history: list,
    rag_docs: list,
    tool_definitions: list,
    max_tokens: int = 128000,  # GPT-4o limit
    reserve_for_output: int = 4096
) -> dict:
    """Calculate token budget per context layer"""
    available = max_tokens - reserve_for_output
    budget = {
        "system": count_tokens(system_prompt),
        "tools": sum(count_tokens(str(t)) for t in tool_definitions),
        "history": sum(count_tokens(str(m)) for m in conversation_history),
        "rag": sum(count_tokens(d.page_content) for d in rag_docs),
    }
    total_used = sum(budget.values())
    budget["remaining"] = available - total_used
    budget["utilization"] = f"{(total_used / available) * 100:.1f}%"
    return budget

# Example usage
budget = budget_context_window(
    system_prompt=system_msg,
    conversation_history=messages,
    rag_docs=retrieved_docs,
    tool_definitions=tools,
)
print(budget)
# {'system': 850, 'tools': 2400, 'history': 3200,
#  'rag': 4500, 'remaining': 113046, 'utilization': '8.9%'}

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

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

class ContextManager:
    """Production context management for LLM applications"""

    def __init__(self, max_tokens=128000, output_reserve=4096):
        self.max_tokens = max_tokens
        self.output_reserve = output_reserve
        self.available = max_tokens - output_reserve

    # Priority tiers for context elements
    PRIORITY = {
        "system_prompt": 1,      # Never trim
        "tool_definitions": 2,   # Rarely trim
        "user_query": 3,         # Never trim
        "recent_messages": 4,    # Keep last 3-4
        "rag_documents": 5,      # Trim by relevance
        "older_messages": 6,     # Summarize first
        "user_preferences": 7,   # Include if space
    }

    def assemble_context(self, components: dict) -> list:
        """Assemble context respecting token budget"""
        result = []
        tokens_used = 0

        # Sort by priority (lower number = higher priority)
        sorted_items = sorted(
            components.items(),
            key=lambda x: self.PRIORITY.get(x[0], 99)
        )

        for name, content in sorted_items:
            content_tokens = count_tokens(str(content))
            if tokens_used + content_tokens <= self.available:
                result.append(content)
                tokens_used += content_tokens
            else:
                # Try to compress instead of dropping
                compressed = self._compress(content,
                    budget=self.available - tokens_used)
                if compressed:
                    result.append(compressed)
                    tokens_used += count_tokens(str(compressed))
                # Else: drop this component entirely
        return result

    def _compress(self, content, budget):
        """Compress content to fit within budget"""
        if count_tokens(str(content)) <= budget:
            return content
        # Use LLM to summarize if budget allows
        if budget > 200:
            summary = summarize(content, max_tokens=budget)
            return summary
        return None  # Cannot fit

Caching סמנטי — איך חוסכים 50-90% בעלויות API

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

from langchain_community.cache import RedisSemanticCache
from langchain_openai import OpenAIEmbeddings
import langchain

# Configure semantic caching
langchain.llm_cache = RedisSemanticCache(
    redis_url="redis://localhost:6379",
    embedding=OpenAIEmbeddings(model="text-embedding-3-small"),
    score_threshold=0.92  # High similarity required
)

# Now identical or near-identical queries hit cache
# "What is context engineering?" and
# "Explain context engineering to me" ->
# Same cached result (saves API call + latency)

מה שיפה ב-caching סמנטי זה שהוא מזהה כששאילתות שונות בניסוח אבל זהות במשמעות. במקום התאמה מילולית (שמחמיצה את רוב המקרים), הוא משתמש ב-embeddings כדי למצוא דמיון סמנטי. ארגונים שהטמיעו את זה מדווחים על חיסכון של 50-90% בעלויות API ושיפור משמעותי בזמני תגובה. זה ממש low-hanging fruit.

Memory Blocks — זיכרון ארוך טווח למערכות סוכניות

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

# Memory Block architecture concept
class MemoryBlock:
    """Reserved context window section for persistent memory"""

    def __init__(self, name: str, max_tokens: int):
        self.name = name
        self.max_tokens = max_tokens
        self.content = ""

    def update(self, new_info: str, llm):
        """Intelligently update memory block"""
        prompt = f"""Current memory block ({self.name}):
{self.content}

New information to integrate:
{new_info}

Update the memory block by:
1. Adding new relevant facts
2. Removing outdated information
3. Keeping total under {self.max_tokens} tokens
4. Prioritizing the most actionable information"""

        self.content = llm.invoke(prompt).content

# Example: Agent with structured memory blocks
class AgentMemory:
    def __init__(self):
        self.core_memory = MemoryBlock(
            "core_facts", max_tokens=500)    # Key user info
        self.working_memory = MemoryBlock(
            "current_task", max_tokens=1000)  # Active task
        self.archival = VectorStore()         # Unlimited external

    def get_context(self) -> str:
        return f"""## Core Memory
{self.core_memory.content}

## Working Memory
{self.working_memory.content}

## Retrieved from Archive
{self.archival.search(current_query, k=3)}"""

ההבדל העיקרי בין Memory Blocks לזיכרון רגיל: הסוכן עצמו יכול להחליט מה לזכור ומתי לשלוף. זה לא עוד pipeline קבוע של RAG — זו אוטונומיה קוגניטיבית אמיתית. הסוכן מנהל את הזיכרון שלו כמו שאנחנו מנהלים פתקים — כותב דברים חשובים, מוחק מה שלא רלוונטי, ומחפש כשצריך.

הנדסת הגדרות כלים: מה שרוב אנשים מפספסים

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

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

from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings

class DynamicToolSelector:
    """Select relevant tools dynamically using RAG"""

    def __init__(self, all_tools: list):
        # Store tool definitions in vector store
        tool_docs = [
            f"Tool: {t.name}\nDescription: {t.description}\n"
            f"Use when: {t.use_cases}"
            for t in all_tools
        ]
        self.tool_index = FAISS.from_texts(
            tool_docs,
            OpenAIEmbeddings(model="text-embedding-3-small")
        )
        self.tool_map = {t.name: t for t in all_tools}

    def select_tools(self, query: str, max_tools: int = 8):
        """Return only relevant tools for the current query"""
        results = self.tool_index.similarity_search(
            query, k=max_tools)
        selected_names = [
            r.page_content.split("\n")[0].replace("Tool: ", "")
            for r in results
        ]
        return [self.tool_map[n] for n in selected_names
                if n in self.tool_map]

# Usage: Instead of giving the LLM 100+ tools...
selector = DynamicToolSelector(all_available_tools)
relevant_tools = selector.select_tools(
    "Calculate quarterly revenue from sales data"
)
# Returns only 5-8 relevant tools instead of 100+

הגישה הזו מקטינה באופן דרמטי את כמות הטוקנים שמוקדשת להגדרות כלים, ובמקביל משפרת את הדיוק כי המודל לא צריך "לבחור" מתוך רשימה ארוכה מדי. פחות אופציות = החלטות טובות יותר. (נכון גם לבני אדם, אגב.)

פטרן Context Orchestrator — חיבור הכל יחד

עכשיו מגיע החלק הכיפי. בואו נראה איך מחברים את כל הטכניקות לפטרן ייצור מלא. הרעיון: Context Orchestrator — שכבת תיאום מרכזית שמרכיבה את ההקשר הדינמי לפני כל קריאה ל-LLM:

from dataclasses import dataclass
from langchain_openai import ChatOpenAI

@dataclass
class ContextConfig:
    max_total_tokens: int = 128000
    output_reserve: int = 4096
    system_prompt_budget: int = 2000
    tool_budget: int = 3000
    rag_budget: int = 8000
    history_budget: int = 6000
    memory_budget: int = 2000

class ContextOrchestrator:
    """Central orchestrator for dynamic context assembly"""

    def __init__(self, config: ContextConfig):
        self.config = config
        self.context_manager = ContextManager(
            config.max_total_tokens,
            config.output_reserve
        )
        self.memory = AgentMemory()
        self.tool_selector = DynamicToolSelector(all_tools)
        self.retriever = create_retriever()
        self.cache = SemanticCache()

    async def build_context(self, user_query: str,
                            conversation_history: list
                           ) -> dict:
        """Assemble optimized context for LLM call"""

        # 1. Check semantic cache first
        cached = await self.cache.lookup(user_query)
        if cached:
            return {"response": cached, "from_cache": True}

        # 2. Parallel context assembly
        import asyncio
        rag_task = asyncio.create_task(
            self.retriever.ainvoke(user_query))
        tools_task = asyncio.create_task(
            asyncio.to_thread(
                self.tool_selector.select_tools, user_query))
        memory_task = asyncio.create_task(
            asyncio.to_thread(
                self.memory.get_context))

        rag_docs, tools, memory_context = await asyncio.gather(
            rag_task, tools_task, memory_task)

        # 3. Compress history if needed
        compressed_history = self._manage_history(
            conversation_history)

        # 4. Assemble with priority-based budget
        context = self.context_manager.assemble_context({
            "system_prompt": self._get_system_prompt(),
            "tool_definitions": tools,
            "user_query": user_query,
            "recent_messages": compressed_history,
            "rag_documents": rag_docs,
            "user_preferences": memory_context,
        })

        return {
            "context": context,
            "tools": tools,
            "from_cache": False,
            "token_budget": self.context_manager.last_budget
        }

שימו לב לשלושה דברים חשובים בדפוס הזה:

  1. בדיקת cache סמנטי לפני כל עבודה אחרת — חוסך עלויות וזמן
  2. שליפה מקבילית של RAG, כלים, וזיכרון — מקצר latency משמעותית
  3. הרכבה מבוססת עדיפויות — רכיבים קריטיים תמיד נכנסים, אופציונליים נדחסים או נפלטים לפי הצורך

מלכודות נפוצות ואיך להימנע מהן

אחרי שבניתי (ולפעמים גם שברתי) עשרות מערכות מבוססות LLM בייצור, הנה הטעויות שאני רואה שוב ושוב:

1. "יותר הקשר = תשובות טובות יותר"

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

תנו למודל את המינימום הנדרש, לא את המקסימום האפשרי.

2. התעלמות מסדר המידע בחלון ההקשר

למודלים יש הטיה לחדשנות (recency bias) — הם נותנים יותר משקל למידע שמופיע בסוף חלון ההקשר. מחקרים מראים ששאילתות בסוף הפרומפט משפרות את איכות התשובה ב-עד 30%. כלל אצבע פשוט: מסמכים ארוכים למעלה, הוראות ושאילתות למטה.

3. חוסר ניטור שימוש בטוקנים

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

4. שכחת מקרי קצה של Structured Output

אם אתם מצפים ל-JSON מובנה מהמודל, טפלו תמיד ב: סירובים (refusals), חיתוך (truncation), מערכים ריקים, ובלבול enum. בנו שרשרת fallback — אף ספק לא אמין ב-100%, לא משנה מה כתוב בדוקומנטציה.

5. אי-בידוד הקשר במערכות מרובות-סוכנים

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

כלים וספריות מומלצים ב-2026

כלישימושהערות
LangChain / LangGraphאורקסטרציה מלאה של הקשרהסטנדרט בתעשייה לניהול state ו-context
LlamaIndexאינדוקס ושליפת מסמכיםמצוין ל-RAG מתקדם
Pydantic / Zodולידציה של פלט מובנהחובה — גם כשה-API מבטיח schema compliance
Instructorהפקת פלט מובנה מ-LLMתומך ב-15+ ספקי LLM עם API אחיד
Redis + EmbeddingsCaching סמנטיחיסכון של 50-90% בעלויות API
LangSmithTracing ודיבאגינגObservability מלא על כל שלב בפייפליין
MemGPT / LettaMemory Blocksזיכרון ארוך טווח אוטונומי לסוכנים

שאלות נפוצות

מה ההבדל בין הנדסת הקשר להנדסת פרומפטים?

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

האם הנדסת הקשר רלוונטית רק למערכות גדולות?

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

כמה טוקנים צריך להקצות לכל שכבה בחלון ההקשר?

אין כלל אחד שמתאים לכולם, אבל הנה נקודת התחלה טובה למודלים עם חלון של 128K טוקנים: System Prompt — 1,000-2,000 טוקנים, הגדרות כלים — 2,000-4,000, היסטוריית שיחה — 4,000-8,000, מסמכי RAG — 4,000-10,000, זיכרון — 1,000-2,000, ושמירת 4,096 לפחות לפלט. אבל חשוב — מדדו, אל תנחשו. עקבו אחרי שימוש בטוקנים בייצור ואופטימזו לפי מה שרואים בפועל.

איך הנדסת הקשר משתלבת עם RAG?

RAG הוא אחד ממרכיבי הליבה של הנדסת הקשר — ספציפית, הוא מיישם את אסטרטגיית ה"בחר" (Select). אבל RAG לבדו לא מספיק. הנדסת הקשר מוסיפה שכבות של דחיסה (סיכום מסמכים ארוכים), עדיפויות (אילו chunks חשובים יותר), בידוד (לא כל סוכן צריך את אותם מסמכים), ו-caching סמנטי (חיסכון בשליפות חוזרות).

מה הטעות הנפוצה ביותר בהנדסת הקשר?

הטעות מספר 1 היא להניח ש"יותר הקשר = תשובות טובות יותר". מחקרים מראים שביצועי LLM-ים יורדים כשחלון ההקשר מוצף במידע. מודלים מתבלבלים, מתעלמים ממידע חשוב שנמצא באמצע (ה"אפקט של האגם האבוד"), ומייצרים hallucinations. הכלל: תנו למודל את המינימום הנדרש עם האות-לרעש הגבוה ביותר.

אודות הכותב Editorial Team

Our team of expert writers and editors.