การจัดการสถานะระหว่างขั้นตอนการเรียกใช้
คงตัวแปร โครงสร้างข้อมูลแบบตาราง และไลบรารีที่นำเข้าไว้ตลอดหลายขั้นตอนการเรียกใช้โค้ด เพื่อให้เอเจนต์ต่อยอดจากผลลัพธ์ก่อนหน้าได้โดยไม่ต้องคำนวณก่อนหน้านี้ซ้ำ
การจัดการสถานะระหว่างขั้นตอนการเรียกใช้ เป็นบทเรียน AI Engineering Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 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!การคงสถานะด้วยไฟล์
แนวทางที่ง่ายและนำไปใช้ได้หลากหลายที่สุดคือการบันทึกสถานะลงในไฟล์ เมื่อจบแต่ละบล็อกโค้ด เอเจนต์จะบันทึก DataFrames พจนานุกรม หรือออบเจ็กต์อื่น ๆ ลงในไฟล์ในไดเรกทอรีพื้นที่ทำงาน จากนั้นการทำงานรอบถัดไปจะโหลดไฟล์เหล่านั้นกลับมา Parquet เหมาะอย่างยิ่งสำหรับ DataFrames, 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 ในการทำงานแต่ละรอบ เพื่อให้ 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 ขึ้นไป) ให้ใช้เคอร์เนลที่คงอยู่ เพื่อให้ DataFrame อยู่ในหน่วยความจำข้ามการทำงานแต่ละรอบ แทนการแปลงเป็นข้อมูลอนุกรมและแปลงกลับทุกครั้ง
# 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 ทราบอย่างถูกต้องว่าไฟล์ใดพร้อมใช้งานและขั้นตอนใดเสร็จแล้วในแต่ละรอบ บทถัดไปเราจะสร้างเอเจนต์วิเคราะห์ข้อมูลแบบเต็มรูปแบบ
คำถามที่พบบ่อย
บทเรียน “การจัดการสถานะระหว่างขั้นตอนการเรียกใช้” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “การจัดการสถานะระหว่างขั้นตอนการเรียกใช้” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส AI Engineering Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส AI Engineering Academy มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “การจัดการสถานะระหว่างขั้นตอนการเรียกใช้”
คงตัวแปร โครงสร้างข้อมูลแบบตาราง และไลบรารีที่นำเข้าไว้ตลอดหลายขั้นตอนการเรียกใช้โค้ด เพื่อให้เอเจนต์ต่อยอดจากผลลัพธ์ก่อนหน้าได้โดยไม่ต้องคำนวณก่อนหน้านี้ซ้ำ คุณปฏิบัติ AI Engineering Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน AI Engineering Academy หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน AI Engineering Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน
บทเรียน “การจัดการสถานะระหว่างขั้นตอนการเรียกใช้” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน AI Engineering Academy นี้ได้ไหม
ได้ บทเรียน AI Engineering Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- ลูปการเรียกใช้โค้ด
- การทำแซนด์บ็อกซ์ด้วย Docker และ RestrictedPython
- การจัดการสถานะระหว่างขั้นตอนการเรียกใช้
- การสร้างเอเจนต์วิเคราะห์ข้อมูล