AI Engineering Academy · درس

إدارة الحالة عبر خطوات التنفيذ

احفظوا المتغيرات وأطر البيانات والمكتبات المستوردة عبر خطوات تنفيذ متعددة للشيفرة، كي يتمكن الوكيل من البناء على النتائج السابقة دون إعادة تشغيل الحسابات السابقة.

الدرس 3 من 413 خطوة

إدارة الحالة عبر خطوات التنفيذ درس مجاني في AI Engineering Academy على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في AI Engineering Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة AI Engineering Academy 4 دروس في المجموع.

مشكلة انعدام الحالة

تبدأ كل عملية تنفيذ لحاوية Docker بمفسّر Python جديد. ولا تكون المتغيرات المعرّفة في التكرار 1 موجودة في التكرار 2. ويجبر هذا انعدام الحالة الوكيل على إعادة حساب كل شيء أو إعادة تحميله من البداية في كل خطوة تنفيذ، ما لم تطبّق استراتيجية صريحة لإدارة الحالة من أجل حفظ الحالة واستعادتها عبر التكرارات. وبدون ذلك، يستحيل إجراء التحليلات متعددة الخطوات.

# Iteration 1 - works fine
df = pd.read_csv('data.csv')  # df is in memory
df_cleaned = df.dropna()
print('Rows after cleaning:', len(df_cleaned))

# Iteration 2 - NEW container, df_cleaned is GONE
result = df_cleaned.groupby('category').sum()  # NameError: df_cleaned is not defined
print(result)  # This will fail!

استمرار الحالة المستند إلى الملفات

أبسط نهج وأكثرها قابلية للنقل هو حفظ الحالة في ملفات. في نهاية كل كتلة تعليمات برمجية، يحفظ الوكيل إطارات البيانات والقواميس أو الكائنات الأخرى في ملفات داخل مجلد مساحة العمل. ثم يعيد تحميلها في التكرار التالي. يُعد Parquet مثاليًا لإطارات البيانات، وJSON للقواميس، وpickle لكائنات Python العشوائية (مع أن استخدام pickle من تعليمات برمجية غير موثوقة يمثل خطرًا أمنيًا).

import pandas as pd
import json
from pathlib import Path

WORKSPACE = Path('/workspace')

# Iteration 1: process and SAVE
df = pd.read_csv(WORKSPACE / 'raw_data.csv')
df_cleaned = df.dropna().reset_index(drop=True)
df_cleaned.to_parquet(WORKSPACE / 'cleaned.parquet')  # save for next iteration

stats = {'rows': len(df_cleaned), 'columns': list(df_cleaned.columns)}
with open(WORKSPACE / 'stats.json', 'w') as f:
    json.dump(stats, f)

print('Saved cleaned data:', len(df_cleaned), 'rows')

# Iteration 2: LOAD and continue
df_cleaned = pd.read_parquet(WORKSPACE / 'cleaned.parquet')  # restore state
with open(WORKSPACE / 'stats.json') as f:
    stats = json.load(f)
result = df_cleaned.groupby('category')['value'].sum()
print(result)

تعليم الوكيل استخدام الحالة المستندة إلى الملفات

لا يكفي تطبيق الحالة المستندة إلى الملفات في البنية التحتية لديك؛ إذ يجب أن يعرف LLM هذا الأسلوب وأن يستخدمه باستمرار أيضًا. أدرج تعليمات صريحة في مطالبة النظام: بعد حساب نتيجة ستكون مطلوبة لاحقًا، احفظها دائمًا باسم ملف وصفي، وفي بداية كل تكرار، حمّل دائمًا الملفات التي أُنشئت في الخطوات السابقة قبل المتابعة.

STATE_MANAGEMENT_INSTRUCTIONS = '''
State persistence rules:
- You have a persistent workspace at /workspace/
- After computing any result you will need later, SAVE it to /workspace/
  - DataFrames: use .to_parquet('/workspace/name.parquet')
  - Dicts/lists: use json.dump to /workspace/name.json
  - Text: write to /workspace/name.txt
- At the start of each code block, LOAD the files you need from previous steps
- Use clear, descriptive filenames like 'cleaned_data.parquet', not 'tmp1.parquet'
- When listing files, use: import os; print(os.listdir('/workspace/'))
'''

الحالة المستندة إلى النواة باستخدام عمليات Python مستمرة

البديل عن الحالة المستندة إلى الملفات هو إبقاء عملية Python مستمرة (مثل نواة Jupyter) قيد التشغيل بين التكرارات، وحقن كل كتلة تعليمات برمجية فيها باستخدام exec(). وتظل المتغيرات المعرّفة في تكرار ما متاحة في التكرار التالي. هذا النهج أسرع وأكثر طبيعية، لكنه يتطلب تشغيل عملية مستمرة لكل جلسة وكيل بدلًا من حاويات مؤقتة.

import jupyter_client

class PersistentKernel:
    def __init__(self):
        km, self.kc = jupyter_client.manager.start_new_kernel(kernel_name='python3')
        self.kc.wait_for_ready(timeout=30)
        print('Kernel started')

    def execute(self, code: str, timeout=60) -> tuple[str, str]:
        msg_id = self.kc.execute(code)
        outputs, errors = [], []
        
        while True:
            msg = self.kc.get_iopub_msg(timeout=timeout)
            if msg['msg_type'] == 'stream':
                if msg['content']['name'] == 'stdout':
                    outputs.append(msg['content']['text'])
                else:
                    errors.append(msg['content']['text'])
            if msg['msg_type'] == 'status' and msg['content']['execution_state'] == 'idle':
                break
        
        return ''.join(outputs), ''.join(errors)

    def shutdown(self):
        self.kc.shutdown()

# Variables persist across execute() calls!
kernel = PersistentKernel()
kernel.execute('x = 42')  # x is now defined in kernel
output, _ = kernel.execute('print(x)')  # prints 42 - state persists!
kernel.shutdown()

إدارة الحالة باستخدام كائن حالة

في طبقة منسّق الوكيل (خارج البيئة المعزولة)، احتفظ بـ كائن حالة صريح يتتبع الوضع الحالي لعمل الوكيل: الملفات التي أُنشئت، وما تم حسابه، والخطوة الحالية للوكيل، والهدف النهائي. مرّر ملخصًا لهذا الكائن إلى LLM في كل تكرار حتى يعرف دائمًا موضعه ضمن المهمة ككل.

from dataclasses import dataclass, field
from typing import Any

@dataclass
class ExecutionState:
    task: str
    iteration: int = 0
    workspace_files: list[str] = field(default_factory=list)
    computed_values: dict[str, Any] = field(default_factory=dict)
    completed_steps: list[str] = field(default_factory=list)
    last_output: str = ''

    def to_context_summary(self) -> str:
        return f'''Current task: {self.task}
Iteration: {self.iteration}
Completed steps: {', '.join(self.completed_steps) or 'None yet'}
Workspace files: {', '.join(self.workspace_files) or 'None yet'}
Key values: {self.computed_values}
Last output: {self.last_output[:500]}'''

    def after_execution(self, output: str, step_name: str):
        import os
        self.workspace_files = os.listdir('/workspace')
        self.completed_steps.append(step_name)
        self.last_output = output
        self.iteration += 1

حقن سياق الحالة في المطالبات

في كل مرة تستدعي فيها LLM للحصول على كتلة التعليمات البرمجية التالية، احقن سياق الحالة الحالي في رسالة المستخدم. يزوّد ذلك LLM بمعلومات دقيقة عن الملفات المتاحة، وما تم حسابه، وما لا يزال يتعين إنجازه. وبدون هذا السياق، قد يحاول LLM إعادة إنشاء ملفات موجودة بالفعل أو يتجاوز خطوات اكتملت مسبقًا.

def build_iteration_prompt(task: str, state: ExecutionState, last_observation: str) -> str:
    return f'''ORIGINAL TASK: {task}

CURRENT STATE:
{state.to_context_summary()}

LAST EXECUTION OUTPUT:
{last_observation}

What is the next step? Write Python code to continue. 
Remember:
- Load data from workspace files if needed
- Save any results you will need in future steps
- If the task is complete, say TASK COMPLETE and summarize the result'''

# Use in the main loop
for step_num in range(max_iterations):
    context = build_iteration_prompt(task, state, last_observation)
    response = llm.complete(messages + [{'role': 'user', 'content': context}])
    code = extract_code_block(response)
    if not code:
        break  # done
    output, err = execute_in_sandbox(code)
    state.after_execution(output, step_name=f'step_{step_num}')
    last_observation = format_observation(output, err)

التعامل مع البيانات الوسيطة الكبيرة

غالبًا ما تنتج وكلاء تحليل البيانات مجموعات بيانات وسيطة كبيرة، وتكون إعادة تحميلها في كل تكرار مكلفة. طبّق التحميل الكسول: حمّل البيانات التي تحتاج إليها للخطوة الحالية فقط. استخدم تنسيقات عمودية مثل Parquet، التي تدعم القراءة الفعالة لأعمدة محددة. وبالنسبة إلى مجموعات البيانات الكبيرة جدًا (أكبر من 100MB)، احتفظ بنواة مستمرة بحيث يبقى إطار البيانات في الذاكرة عبر التكرارات بدلًا من إجراء تسلسل له وإلغاء تسلسله في كل مرة.

# Good: load only needed columns
df = pd.read_parquet('/workspace/full_data.parquet', columns=['date', 'revenue', 'region'])

# Bad: load everything even if you only need 2 columns
# df = pd.read_parquet('/workspace/full_data.parquet')  # loads 50 columns you don't need

# Good: filter early before loading full dataset
df = pd.read_parquet('/workspace/full_data.parquet', filters=[('region', '=', 'EMEA')])

# For very large files, tell the LLM about data shape upfront
df_info = {'shape': (1_000_000, 50), 'size_mb': 850, 'columns': [...]}
# Include df_info in state so LLM plans accordingly

إنشاء نقاط تحقق للمهام طويلة التشغيل

بالنسبة إلى المهام التي تمتد عبر تكرارات كثيرة، طبّق إنشاء نقاط التحقق: احفظ دوريًا حالة الوكيل الكاملة (الخطوات المكتملة، وملفات مساحة العمل، والتقدم الحالي) في مخزن دائم حتى يمكن استئناف المهمة بعد حدوث انقطاع. ويكتسب ذلك أهمية خاصة في مهام تحليل البيانات الطويلة التي قد تستغرق أكثر من 30 دقيقة، وقد تنقطع بسبب مشكلات الشبكة أو أخطاء API أو إعادة تشغيل الخادم.

import json
import time

def checkpoint_state(state: ExecutionState, checkpoint_id: str, db):
    data = {
        'task': state.task,
        'iteration': state.iteration,
        'completed_steps': state.completed_steps,
        'workspace_files': state.workspace_files,
        'timestamp': time.time()
    }
    db.execute(
        'INSERT INTO agent_checkpoints (id, state) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET state=excluded.state',
        (checkpoint_id, json.dumps(data))
    )
    print(f'Checkpoint saved at iteration {state.iteration}')

def resume_from_checkpoint(checkpoint_id: str, db) -> ExecutionState | None:
    row = db.execute('SELECT state FROM agent_checkpoints WHERE id = ?', (checkpoint_id,)).fetchone()
    if not row:
        return None
    data = json.loads(row[0])
    state = ExecutionState(task=data['task'])
    state.iteration = data['iteration']
    state.completed_steps = data['completed_steps']
    print(f'Resumed from iteration {state.iteration}')
    return state

تنظيف الحالة بعد الاكتمال

تتراكم الملفات في مساحات عمل الوكلاء وقد يزداد حجمها بمرور الوقت. طبّق دائمًا مرحلة تنظيف تعمل عند اكتمال المهمة أو فشلها: احذف الملفات الوسيطة (cleaned.parquet، tmp_output.csv) واحتفظ فقط بملفات الإخراج النهائية التي تهم المستخدم. وبالنسبة إلى التخزين السحابي، عيّن سياسات دورة حياة تحذف ملفات مساحة العمل تلقائيًا بعد مدة الاحتفاظ.

import shutil
from pathlib import Path

INTERMEDIATE_PATTERNS = ['*.parquet', 'tmp_*.csv', 'step_*.json', 'debug_*.txt']
FINAL_OUTPUT_PATTERNS = ['report.pdf', 'final_*.csv', 'summary.json']

def cleanup_workspace(workspace_dir: str, keep_final=True):
    workspace = Path(workspace_dir)
    final_outputs = []
    
    if keep_final:
        for pattern in FINAL_OUTPUT_PATTERNS:
            final_outputs.extend(workspace.glob(pattern))
        # Move final outputs to output directory
        output_dir = workspace.parent / 'outputs'
        output_dir.mkdir(exist_ok=True)
        for f in final_outputs:
            shutil.move(str(f), str(output_dir / f.name))
    
    # Delete the workspace
    shutil.rmtree(workspace_dir)
    print(f'Workspace cleaned up. Kept {len(final_outputs)} output files.')
    return [str(f) for f in final_outputs]

إصدار الحالة لتصحيح الأخطاء

عندما ينتج وكيل التعليمات البرمجية نتائج خاطئة، تحتاج إلى تتبع سجل تنفيذه لمعرفة موضع الخطأ. طبّق إصدار الحالة بحفظ لقطة من مساحة العمل بعد كل تكرار. يتيح لك ذلك إعادة تشغيل تنفيذ الوكيل، وفحص الحالات الوسيطة، وتحديد التكرار الدقيق الذي حدث فيه الخطأ. خزّن الفروقات فقط (الملفات المتغيرة) لتوفير المساحة.

نافذة السياق مقابل الحالة الخارجية

يوجد تعارض أساسي في تصميم وكلاء التعليمات البرمجية بين وضع الحالة في نافذة سياق LLM (فورية لكنها محدودة) ووضعها في تخزين خارجي (غير محدود لكنه يتطلب إدارة صريحة). الاستراتيجية المثلى هي: الاحتفاظ ببيانات الخطوة الحالية في السياق، والاحتفاظ بالنتائج الوسيطة الكبيرة في الملفات، والاحتفاظ بهدف المهمة وخطتها عالية المستوى في السياق، والاحتفاظ دائمًا بالبيانات الخام على القرص. يدير LLM المهمة، بينما يدير نظام الملفات البيانات.

تحقق سريع

اختبر مدى فهمك لإدارة الحالة عبر خطوات تنفيذ التعليمات البرمجية من هذا الدرس.

مراجعة الدرس

تعلمت في هذا الدرس أن استمرار الحالة المستند إلى الملفات باستخدام Parquet وJSON هو الطريقة الأكثر قابلية للنقل لمشاركة الحالة بين عمليات تنفيذ الحاويات المؤقتة، وأن النوى المستمرة تلغي كلفة إعادة التحميل بالحفاظ على عملية Python حية عبر التكرارات، وأن حقن سياق الحالة يزوّد LLM بمعرفة دقيقة بالملفات المتاحة والخطوات المكتملة في كل تكرار. بعد ذلك، سنبني وكيلًا كاملًا لتحليل البيانات.

البدء مجانًا

تعلم Python مع معلم ذكاء اصطناعي — مجانًا

اكتب وقم بتشغيل أكوادك الفعلية في المتصفح، واحصل على مساعدة فورية من معلم ذكاء اصطناعي متاح 24/7، واستمر من حيث توقفت على الويب أو في التطبيق.

الدورات
30
الدروس
120

الأسئلة الشائعة

هل درس «إدارة الحالة عبر خطوات التنفيذ» مجاني؟

نعم — نص درس «إدارة الحالة عبر خطوات التنفيذ» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة AI Engineering Academy، انتقل إلى CoddyKit PRO. تتضمن دورة AI Engineering Academy 4 دروس في المجموع.

ماذا ستتعلم في «إدارة الحالة عبر خطوات التنفيذ»؟

احفظوا المتغيرات وأطر البيانات والمكتبات المستوردة عبر خطوات تنفيذ متعددة للشيفرة، كي يتمكن الوكيل من البناء على النتائج السابقة دون إعادة تشغيل الحسابات السابقة. تتمرن على AI Engineering Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ AI Engineering Academy؟

لا تُشترط خبرة سابقة. AI Engineering Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.

كم من الوقت يستغرق درس «إدارة الحالة عبر خطوات التنفيذ»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس AI Engineering Academy هذا؟

نعم. كل درس في AI Engineering Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. حلقة تنفيذ الشيفرة
  2. العزل باستخدام Docker وRestrictedPython
  3. إدارة الحالة عبر خطوات التنفيذ
  4. بناء وكيل لتحليل البيانات
← العودة إلى AI Engineering Academy