สร้าง Engineering Copilot แบบ Stateful ด้วย Letta

บทช่วยสอนนี้จะพาคุณไปสร้าง Engineering Copilot แบบ stateful ด้วย Letta เพื่อสาธิตให้เห็นว่า agentic memory สามารถพัฒนาไปได้ไกลกว่าการสนทนาผ่าน prompt แบบเดิม เราจะเริ่มจากการสร้าง Core Memory Blocks ที่ตรวจสอบได้ ซึ่ง Agent สามารถอัปเดตข้อมูลได้ด้วยตัวเอง จากนั้นจะเปิดช่องทางให้เข้าถึง Block เหล่านั้นเพื่อทำ Programmatic State Synchronization และแชร์ข้อมูลระหว่างหลาย Agent ร่วมกัน
นอกจากนี้ เรายังขยายสถาปัตยกรรมด้วย Archival Vector Memory สำหรับการทำ Semantic Retrieval, สร้าง Custom Tool ฝั่ง Server เพื่อคำนวณความเสี่ยงแบบ Deterministic, ระบบ Streaming เพื่อสังเกตการณ์การประมวลผลของ Agent, การจัดเก็บประวัติข้อความฝั่ง Server เพื่อการสนทนาที่ต่อเนื่อง และการส่งออกไฟล์ .af เพื่อความสะดวกในการเคลื่อนย้าย (Portability)
องค์ประกอบเหล่านี้คือการแสดงสถาปัตยกรรมหน่วยความจำที่ใช้งานได้จริง ซึ่งสถานะถาวร (Persistent State), การดึงข้อมูล (Retrieval), เครื่องมือ, การทำงานร่วมกัน และร่องรอยการประมวลผล (Execution Traces) ทำงานสอดประสานกันเป็นระบบ Agentic ที่ครบวงจร
import subprocess, sys
subprocess.run([sys.executable, "-m", "pip", "install", "-q", "-U", "letta-client"], check=True)
import os, json, time, textwrap
from getpass import getpass
from letta_client import Letta
def flex(*attempts, label=""):
err = None
for fn in attempts:
try:
return fn()
except Exception as e:
err = e
raise RuntimeError(f"All call variants failed for {label or 'call'}: {err}") from err
API_KEY = os.environ.get("LETTA_API_KEY") or getpass("Letta API key (platform.letta.com/api-keys): ")
BASE_URL = os.environ.get("LETTA_BASE_URL", "").strip()
client = flex(
lambda: Letta(base_url=BASE_URL, api_key=API_KEY) if BASE_URL else Letta(api_key=API_KEY),
lambda: Letta(base_url=BASE_URL, token=API_KEY) if BASE_URL else Letta(token=API_KEY),
label="client init",
)
def hr(title):
print("\n" + "=" * 92 + f"\n {title}\n" + "=" * 92)
def as_text(content):
if isinstance(content, str):
return content
if isinstance(content, list):
return "".join(getattr(p, "text", "") or "" for p in content)
return str(content or "")
def show(response, hide_reasoning=False):
for m in getattr(response, "messages", response) or []:
kind = getattr(m, "message_type", None) or getattr(m, "type", "?")
if kind == "reasoning_message" and not hide_reasoning:
print("🧠 reasoning :", textwrap.shorten(getattr(m, "reasoning", "") or "", 220))
elif kind == "tool_call_message":
tc = getattr(m, "tool_call", None)
print("🔧 tool call :", getattr(tc, "name", "?"), textwrap.shorten(str(getattr(tc, "arguments", "")), 200))
elif kind == "tool_return_message":
print("↩️ tool ret :", textwrap.shorten(str(getattr(m, "tool_return", "")), 260))
elif kind == "assistant_message":
print("🤖 assistant :", as_text(getattr(m, "content", "")))
elif kind == "approval_request_message":
print("⏸️ approval : agent is waiting for you to approve a tool call")
u = getattr(response, "usage", None)
if u:
print(f" ↳ steps={getattr(u, 'step_count', '?')} tokens={getattr(u, 'total_tokens', '?')}")
def chat(agent_id, text, max_steps=12, quiet=False):
r = flex(
lambda: client.agents.messages.create(agent_id=agent_id, input=text, max_steps=max_steps),
lambda: client.agents.messages.create(agent_id=agent_id, input=text),
lambda: client.agents.messages.create(agent_id=agent_id,
messages=[{"role": "user", "content": text}]),
label="messages.create",
)
if not quiet:
print(f"\n👤 user : {text}")
show(r)
return r
PREFERRED = ["openai/gpt-4.1", "openai/gpt-4o-mini", "anthropic/claude-sonnet-4-20250514"]
try:
handles = [getattr(m, "handle", None) for m in client.models.list()]
MODEL = next((h for h in PREFERRED if h in handles), None) or handles[0]
except Exception:
MODEL = PREFERRED[0]
print(f"✅ connected · model handle = {MODEL}")
hr("1. Create a stateful agent with custom memory blocks")
def make_agent(**kw):
return flex(
lambda: client.agents.create(model=MODEL, embedding="openai/text-embedding-3-small", **kw),
lambda: client.agents.create(model=MODEL, **kw),
label="agents.create",
)
copilot = make_agent(
name=f"copilot-{int(time.time())}",
description="Engineering copilot with inspectable long-term memory",
tags=["tutorial", "copilot"],
memory_blocks=[
{"label": "persona",
"value": "I am Mnemo, an engineering copilot. I am terse, concrete, and I never "
"invent facts — if I don't know something I say so and then go look it up.",
"limit": 3000},
{"label": "human",
"description": "Durable facts about the engineer I work with: name, role, stack, "
"preferences, working style. Update this whenever I learn something stable.",
"value": "Unknown so far.",
"limit": 3000},
{"label": "project_state",
"description": "Live state of the project we are shipping: milestones, blockers, "
"decisions and their rationale. Keep it dense and current; delete stale lines.",
"value": "(empty)",
"limit": 5000},
{"label": "lessons",
"description": "Hard-won lessons and mistakes to never repeat. Append-only, one line each.",
"value": "(empty)",
"limit": 2000},
],
)
print("agent id:", copilot.id)
def get_block(agent_id, label):
return flex(
lambda: client.agents.blocks.retrieve(agent_id=agent_id, block_label=label),
lambda: client.agents.blocks.retrieve(agent_id, label),
label="agents.blocks.retrieve",
)
def set_block(agent_id, label, value):
return flex(
lambda: client.agents.blocks.update(agent_id=agent_id, block_label=label, value=value),
lambda: client.agents.blocks.modify(agent_id=agent_id, block_label=label, value=value),
lambda: client.agents.blocks.update(agent_id, label, value=value),
label="agents.blocks.update",
)
def dump_memory(agent_id, title="core memory"):
blocks = flex(lambda: client.agents.blocks.list(agent_id=agent_id),
lambda: client.agents.blocks.list(agent_id), label="agents.blocks.list")
print(f"\n──── {title} ────")
for b in blocks:
ro = " [read-only]" if getattr(b, "read_only", False) else ""
print(f"▸ {b.label}{ro} ({len(b.value or '')}/{getattr(b, 'limit', '?')} chars)")
print(textwrap.indent(textwrap.fill(b.value or "", 100), " "))
return blocks
dump_memory(copilot.id, "core memory at birth")ขั้นตอนเบื้องต้นประกอบด้วยการตั้งค่า Letta Client, ติดตั้ง SDK และเพิ่มส่วนช่วยเหลือด้านความเข้ากันได้ (Compatibility Helpers) เพื่อจัดการความแตกต่างของ API ในแต่ละเวอร์ชัน จากนั้นเราจะเลือกโมเดลที่พร้อมใช้งานและสร้าง Engineering Copilot ที่มีโครงสร้าง Core Memory Blocks เฉพาะตัว พร้อมตรวจสอบสถานะหน่วยความจำเริ่มต้นเพื่อกำหนดบริบทถาวรให้กับ Agent ของเรา
ความคิดเห็น (0)
เข้าสู่ระบบเพื่อร่วมแสดงความเห็น
สมัครสมาชิกมาเป็นคนแรกที่แสดงความเห็นกันเลยโบร
