- variant ที่ใช้ได้
- mtb-mpy เท่านั้น (ไม่มีการลิงก์ libbento_mpy.a เมื่อ BENTO_HAS_MPY=0)
ไม่รวมอยู่ในชุดเอกสารของ mtb-only
เป้าหมายของหัวข้อนี้
สร้าง context ของคำขอจากเซสชันตามระเบียบวินัยที่ผู้เรียกเป็นเจ้าของบัฟเฟอร์ เดินชุด 3 ขั้นของ HTTPS connect → post → disconnect พร้อมการตรวจที่ถูกต้อง และ bring up backend ของ bento_link ตามลำดับที่กำหนดไว้ ตรวจสอบผลด้วยการเดินทางไป-กลับหนึ่งรอบของ ask()
ลำดับการทำงานจริงของเฟิร์มแวร์
การเพิ่มข้อความในเซสชัน เนื้อหาปิดท้ายด้วย NUL บทบาทที่พบในโค้ดที่ส่งมอบจริงคือ USER, ASSISTANT, SYSTEM และ TOOL ส่วนรอบของ assistant จะต่อท้ายเข้าไปหลัง claw_cb_success() เท่านั้น:
s_agent_state = CLAW_STATE_IDLE;
snprintf(s_last_result, sizeof(s_last_result), "ok (%d bytes)", resp_len);
claw_ipc_send_ui(CLAW_UI_LAST_RESULT, s_last_result, strlen(s_last_result));
claw_ipc_send_ui(CLAW_UI_STATUS_TEXT, "Online (HTTPS)", 14);
return mp_obj_new_str(s_work_small, (size_t)resp_len);
การสร้าง context ผู้เรียกเป็นผู้จัดหาบัฟเฟอร์เอง (s_work_large[3072] / s_work_small[512], modbentoclaw.c:237-238) โดยเผื่อพื้นที่ไว้ 256 ไบต์อย่างจงใจ ผลลัพธ์ ไม่ผ่านการ escape แบบ JSON และต้องผ่าน json_escape ก่อนเข้าสู่ body ของคำขอ บัฟเฟอร์ทั้งสองห้ามซ้อนทับกัน:
char *esc_p = s_work_small;
size_t ep_max = sizeof(s_work_small) / 2;
size_t ep_len = json_escape(esc_p, ep_max, prompt,
prompt_len > 200 ? 200 : prompt_len);
char *esc_c = s_work_small + ep_max;
size_t ec_max = sizeof(s_work_small) / 2;
size_t ec_len = json_escape(esc_c, ec_max,
s_work_large, ctx_len > 200 ? 200 : ctx_len);
int json_len = snprintf(s_work_large, sizeof(s_work_large),
"{\"prompt\":\"%.*s\",\"context\":\"%.*s\"}",
(int)ep_len, esc_p, (int)ec_len, esc_c);
ชุดสามขั้นของ transport ตรวจการเชื่อมต่อ ก่อน circuit breaker ที่จุดเรียก ask ทุกจุด:
static mp_obj_t bentoclaw_ask(mp_obj_t prompt_obj) {
bentoclaw_ensure_init();
mp_raise_msg(&mp_type_RuntimeError,
MP_ERROR_TEXT("not connected — call bentoclaw.connect() first"));
}
mp_raise_msg(&mp_type_RuntimeError,
MP_ERROR_TEXT("circuit breaker open"));
}
claw_https_post() คืนจำนวนไบต์ที่เขียนลง resp หรือ ค่าติดลบเมื่อล้มเหลว บัฟเฟอร์คำตอบห้ามชี้ทับบัฟเฟอร์คำขอ:
int json_len = snprintf(s_work_large, sizeof(s_work_large),
"{\"prompt\":\"%.*s\",\"context\":\"%.*s\"}",
(int)ep_len, esc_p, (int)ec_len, esc_c);
int resp_len =
claw_https_post(
"/ai/v1/chat", s_work_large, (
size_t)json_len,
s_work_small, sizeof(s_work_small));
if (resp_len < 0) {
s_agent_state = CLAW_STATE_ERROR;
s_error_count++;
claw_ipc_send_ui(CLAW_UI_ERROR, "Backend error", 13);
snprintf(s_last_result, sizeof(s_last_result), "error");
claw_ipc_send_ui(CLAW_UI_LAST_RESULT, s_last_result, strlen(s_last_result));
mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("HTTPS request failed"));
}
การ connect และ disconnect พร้อมการจับคู่ trust อยู่ใน H2 — safety gate ของ claw
สถานะเซสชันและการล้าง claw_session_count() ป้อนค่าให้ dict สถานะ ส่วน claw_session_clear() จับคู่กับการล้างฝั่ง UI:
mp_obj_dict_store(MP_OBJ_FROM_PTR(d),
MP_OBJ_NEW_QSTR(MP_QSTR_session_msgs),
static mp_obj_t bentoclaw_session_clear(void) {
bentoclaw_ensure_init();
if (claw_ipc_initialized) {
claw_ipc_send_ui(CLAW_UI_CLEAR, "", 0);
}
return mp_const_none;
}
backend ลำดับที่กำหนดไว้คือ bento_link_ipc_init() → bento_link_get("ipc") → ตรวจ NULL → ต่อเข้ากับ adapter:
static void ensure_inited(void)
{
if (s_inited) {
return;
}
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("model link init failed"));
}
s_adapter.base.send = adapter_send;
s_adapter.base.register_receive_cb = adapter_register_receive;
if (s_adapter.link == NULL) {
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("ipc link unavailable"));
}
s_iface.IPC = &s_adapter.base;
model_type_t model = { .va_model = DEEPCRAFT_VA_MODEL };
deepcraft_engine_init(&s_engine, model, &s_iface);
deepcraft_engine_set_event_cb(&s_engine, engine_event_cb);
วนอ่าน backend ที่ลงทะเบียนไว้จนถึง BENTO_LINK_MAX_BACKENDS และหยุดเมื่อพบ NULL:
static mp_obj_t deepcraft_links(void)
{
ensure_inited();
uint32_t n = 0;
if (l == NULL) {
break;
}
items[n++] = mp_obj_new_str(l->
name, strlen(l->
name));
}
return mp_obj_new_tuple(n, items);
}
ทีละขั้น
ขั้นที่ 1 — เดินทางไป-กลับหนึ่งรอบ
>>> import bentoclaw
>>> bentoclaw.ask("What is on the display?")
สิ่งที่ควรสังเกต สตริงคำตอบ และ bentoclaw.status()['session_msgs'] เพิ่มขึ้นสอง (user + assistant) สตริงผลลัพธ์ล่าสุด ok (d bytes) ถูกดันไปยังหน้า BentoClaw บน CM55 (modbentoclaw.c:471-473) หากลิงก์ล่ม จะได้ RuntimeError จากตัวกันของ ask ก่อนที่ breaker จะบันทึกอะไรลงไป
ขั้นที่ 2 — ล้างแล้วยืนยัน
>>> bentoclaw.session_clear()
>>> bentoclaw.status()['session_msgs']
0
สิ่งที่ควรสังเกต จำนวนเป็น 0 และบทสนทนาบนหน้า CM55 ถูกล้างในการเรียกครั้งเดียวกัน
ขั้นที่ 3 — ตรวจดู backend
สิ่งที่ควรสังเกต การ import สำเร็จก็ต่อเมื่อ bento_link_get("ipc") คืนค่าที่ไม่ใช่ NULL ตอน init ของโมดูล snippet ด้านบนคือเส้นทางที่ทำงานจริง
ตัวอย่างที่เขียนขึ้นเอง (ไม่มี call site ในของที่ส่งมอบ)
bento_link_ipc_query — ไม่มีการเรียกในเทมเพลตเลย และไม่มีการประกาศส่งมาด้วย (bento_secure_undeclared.h:11) ผู้ใช้ไลบรารีจึงต้องเขียน extern เอง bento_link_register — เป็น API สำหรับผู้เขียน backend สร้างแบบตามการลงทะเบียนตัวเองที่เก็บไว้ใน archive ที่ bento_link_ipc.c:245 ส่วน claw_session_flush, claw_session_dirty, claw_memory_get — ไม่มีผู้เรียกที่ใดเลย ตัวอย่างของทั้งสามในคู่มืออ้างอิง mpy_secure มีป้ายกำกับว่าเขียนขึ้นเอง และไม่มีสิ่งใดในบทนี้ขึ้นกับทั้งสาม
กับดัก
- Warning
- rlen > 0 เป็นการตรวจที่ผิด สำหรับ claw_https_post() (จุดที่ :2213 ต่างจากการตรวจค่าติดลบที่ถูกต้องที่ :459) ความสำเร็จที่มีความยาวศูนย์เป็นสิ่งที่ถูกต้องตามข้อกำหนด
-
context ที่ไม่ผ่านการ escape ใน body แบบ JSON ทำให้คำขอพังทันทีที่ผู้ใช้พิมพ์เครื่องหมายคำพูดเป็นครั้งแรก
-
การเรียก bento_link_get ก่อน bento_link_ipc_init คืนค่า NULL — ตัว init เป็นผู้ลงทะเบียน backend ให้เอง
กล่อง variant
mtb-mpy เท่านั้น