get_assembly_slots)와 이 로봇 배치에 맞춘 파지 캘리브레이션을 에이전트에게 줬고, 프롬프트는 절차 레시피였다. 실물에서 얻을 수 없는 정보라 걷어내고, 제어를 원본 CaP-X와 같은 pyroki IK → 관절 제어로 바꿨다.is_reachable·관절 상태. 프롬프트는 ~20줄, 물리 사실 4개만. 이미지 없음, 예시 코드 없음, 피드백 턴 없음.1/30 = 0.033 (trial 22). 손으로 짠 oracle은 공식 경로 2/3이라 환경은 풀린다. 4차 3/30과 p = 0.61로 통계적 구분 불가; pi0.5 0.36엔 유의하게 미달(p = 0.0008), LingBot 0.133과는 p = 0.35.2/44 = 0.045(선점·취소된 두 run 합산), +이미지·차분(VDM) 0/30, Agent0(앙상블 9×) 0/7. 관측을 되돌려 줘도 10턴 안에 "놓는 순간"을 푸는 경우는 드물었다. 누적 API 비용 ≈ $210 — 단일 턴은 run당 $6이지만 멀티턴 $45, VDM $85, Agent0 $500+/30 trial 구조라 사용자 결정으로 전부 중단.RuntimeError로 중단 — 나사·재파지 중 다리 넘어짐 16, 삽입 전 정렬 실패 7, 도달성 포기 5. 힌트를 걷어내자 실패가 "안 돌림"에서 "돌리다 넘어뜨림"으로 옮겨갔고, 단일 턴이라 raise가 곧 종료다.CaP-X는 LLM이 API 프리미티브를 조합하는 Python 코드를 한 번 생성해 로봇을 제어하는 code-as-policy 벤치마크다. 원본은 실물에서 RGB-D → 6D pose(SAM3/OWL-ViT), grasp 후보(contact-graspnet), pyroki IK → 관절 이동, gripper 명령으로 동작하고, privileged 모드는 인식만 시뮬레이터 GT로 대체한다.
get_assembly_slots가 채점기의 assembled_rel_poses(다리의 최종 채점 자세)를 그대로 반환, ② sample_grasp_pose가 이 로봇 배치에서 프로브로 잰 손목 한계 상수(q7 ≈ yaw+2.06)로 파지 측 선택, ③ 프롬프트에 "reference policy도 절반만 성공", "controller가 compliant", "5~10 mm에서 채점" 같은 벤치마크 내부 문장과 절차 레시피. 제어도 OSC delta-EE 서보라 원본의 IK 관절 경로와 달랐다.사용자 결정: IK → 관절 제어(원본과 동일) + 물리 사실만 담은 프롬프트. 그 조건에서 다시 잰 것이 이 리포트다.
| 항목 | 원본 CaP-X (nut_assembly 등) | v5 FurnitureBench |
|---|---|---|
| 제어 | pyroki IK → move_to_joints_blocking (Robosuite 관절 제어 / 실물 Franka 동일) | 동일 경로. FurnitureSim 워커(py3.8 Isaac Gym, RPC)가 팔 7 DOF를 런타임에 PhysX position drive(k=2000, d=60)로 전환, 1 rad/s 보간 + settle(0.004 rad). numpy FK(Craig DH + hand −45° + k_ee 0.1034 m)로 IK 잔차 검증 — 시뮬 EE와 0.0000 m 일치 |
| 인식 | privileged: 시뮬 GT 6D pose | 동일. get_object_pose는 bbox 중심 자세(다리 원점은 중심에서 1.25 cm 치우침) + return_bbox_extent |
| 부품 기하 | get_nut_handle_to_center_offset(GT 기하 상수) | get_holes(part) → 구멍 입구 위치 + 축만. 다리 길이로 안착 자세는 에이전트가 구성(원본 nut_assembly가 peg 자세만 주고 삽입 자세를 계산시키는 것과 동급) |
| 파지 | GT 위치 + 고정 쿼터니언 | bbox 중심 + 긴 축에 수직 yaw, 현재 그리퍼 yaw에 가까운 측(캘리브레이션 없음, 거울 파지 유효 명시) |
| 도달성 | (IK 실패 시 best-effort) | is_reachable(pose): IK + FK 잔차(1 cm/5°) + 관절 한계. get_joint_positions(한계 문서화) |
| IK 보완 | — | pyroki vel-cost 해가 큰 회전에서 미달(90° 회전이 10° 부족, 1 cm) → 반환 해에서 재해석(≤4회) + 전역 해 fallback을 API 내부에 내장 |
| 프롬프트 | ~10줄 + 힌트 3~4문장 | ~20줄: 과제·프레임 규약·물리 사실 4개. 절차·허용오차·벤치마크 문장 없음 |
| 규모 | trials 100 / workers 12 | trials 30 / workers 4 (own 쿼터 소진으로 extra QOS) |
로그인 GPU에서 7회 반복(세우기 IK 미달 → 전역 해 fallback, 호버 폐루프 보정 0.7 mm, 입구 4 mm 위에서 놓으면 넘어짐 → 3 mm 안에 넣고 놓기, 나사 −90° 회전 IK 10° 미달 → 반복 IK) 끝에 reward 1.0, 478 step: 안착 +8.3 mm → −90° 나사 4사이클 +6.2 → +4.0 → +1.7 → +0.3 mm, 최종 1.1 mm / 0.0°. 워커 pod 공식 경로(launch.py --use-oracle-code) 2/3 — 실패 1건은 놓기 직전 0.7°/3.1 mm로 정상이었으나 놓은 뒤 안착 실패(낙하 확률). 놓기 깊이·보정 루프는 프롬프트에 쓰지 않았다.
robot_descriptions(GitPython)가 죽음 → conda env에 git 설치 + GIT_PYTHON_GIT_EXECUTABLE. launch.py는 trial이 전부 에러여도 exit 0이라 afterok 의존이 무의미했음 → oracle 스크립트가 성공 0이면 exit 1.async def + blocking requests라 4 worker의 호출이 직렬화 → 200 s 클라이언트 timeout으로 14/16 trial에 프로그램이 없던 1차 실행(118494) 무효·취소. 동기 엔드포인트로 고친 뒤 30 trial이 15분(이전 60분).| 실행 | job | 성공 | 예외 없이 끝난 trial | 코드 생성 | 소요 |
|---|---|---|---|---|---|
| gpt-6-astra, v5 실물-충실 API | 118577 | 1/30 = 0.033 | 2/30 | 30/30 | 15분 (4 workers) |
| 손 oracle (같은 API, 공식 경로) | 118493 | 2/3 | 3/3 | — | 116 s |
| 참고: 4차, 누설 API + 레시피 프롬프트 | 117430 | 3/30 = 0.100 | 30/30 | 30/30 | 60분 |
Fisher exact(양측): v5 vs 4차 p = 0.61 · vs LingBot-VA 0.133 (4/30) p = 0.35 · vs pi0.5 초기 0.24 (12/50) p = 0.025 · vs pi0.5 최종 0.36 (18/50) p = 0.0008 · vs 스크립트 정답 정책 0.45 (9/20) p = 0.0005.
30/30이 is_reachable·get_holes를 사용, 8/30이 get_joint_positions. 프로그램 길이 218~304줄(방어 코드 증가). 28/30은 프로그램 안의 검사문이 raise RuntimeError로 끝냈다.
| 단계 | n | 에이전트가 raise한 메시지 예 |
|---|---|---|
| 나사·재파지 중 다리 기울어짐/이탈 | 16 | "The leg tilted during threading", "The partially threaded leg tipped during regrasp", "The leg slipped away from the hole", "The leg rotated in the wrong threading direction" |
| 삽입 전 정렬 실패 | 7 | "The threaded end is not aligned with the hole", "The threaded end could not be aligned with the hole", "Cannot align the lifted leg above the hole" |
| 도달성 포기 | 5 | "Required pose is outside the robot's reachable workspace" ×3, "No reachable tightening stroke remains", "The wrist cannot start threading from this grasp" |
| 예외 없이 종료, 실패 | 2 | trial 01, 23 |
| 성공 | 1 | trial 22 — 조립이 등록된 뒤에도 계속 조이다 "lost axial alignment"로 raise (채점은 이미 완료) |
같은 코드를 계측 모드로 재실행해 API 호출마다 다리의 기울기·구멍 축 대비 수평 오차·높이를 기록(TSV). 에이전트 프로그램은 raise로 끝나므로 마지막 호출 시점 상태로 분류. 물리가 완전 결정적이진 않아 평가 결과와 다를 수 있고, 2 trial(20·22)은 재생에서 호출 기록이 남지 않았다.
| 도달한 단계 | n | 설명 |
|---|---|---|
| 구멍 위 정렬(기울기 ≤3°, 수평 ≤5 mm)까지 갔으나 삽입 실패 | 19 | 들어 올려 세우고 구멍 위에 맞추는 데까지는 성공(최소 수평 오차 중앙값 ~2 mm). 내리거나 놓는 단계에서 빠져나가 대부분 90°로 넘어짐 |
| 안착(|높이 오차| <12 mm, 기울기 <10°) 후 이탈/넘어짐 | 4 | trial 06·11·17·21 — 나사·재파지 중 쳐서 빠짐. 17은 마지막 상태 1.4° / 0.8 mm / +6 mm(안착만, 미조임) |
| 세우기/운반 중 넘어짐 | 3 | trial 01(최소 기울기 32°), 10, 18 |
| 기타 (정렬 11~12 mm에서 중단) | 2 | trial 14, 30 |
| 재생 기록 없음 | 2 | trial 20, 22(평가에선 성공) |
단가(게이트웨이 /v1/models): gpt-6-astra 입력 $10/M tok, 출력 $50/M tok(reasoning 토큰은 출력에 포함). CaP-X는 usage를 저장하지 않아 대표 호출을 1회씩 실측하고 로그의 호출 수로 환산했다. 이후 job부터는 프록시가 요청별 usage를 기록한다.
| 호출 종류 | prompt tok | completion tok (reasoning) | 시간 | 비용/호출 |
|---|---|---|---|---|
| 단일 턴 초기 프로그램 (v5 프롬프트 8.7 KB) | 1,556 | 3,858 (1,024) | 81 s | $0.21 |
| 멀티턴 결정 프롬프트 (중반, 코드 누적) | 3,652 | 1,163 (146) | 23 s | $0.10 |
| 실행 | LLM 호출 | 추정 비용 (30 trial) |
|---|---|---|
| 단일 턴 (v5 및 v1~v4 각각) | 30 (1/trial) | ≈ $6 |
| real_multiturn | ≈ 11.4/trial | ≈ $45 |
| real_multiturn_vdm (이미지·차분 호출 포함) | ≈ 24/trial | ≈ $80~90 |
| real_agent0 (앙상블 9샘플+종합 매 결정) | ≈ 190/trial | ≈ $500~700, 6~7 h → 7 trial(0 성공) 후 중단(사용자 결정) |
비교: pi0.5 평가는 API 비용 없이 GPU 시간만 든다. 단일 턴 code-as-policy는 에피소드당 $0.2로 값싸지만, 멀티턴·앙상블로 갈수록 성공률보다 비용이 먼저 늘어난다. 이 프로젝트 누적 ≈ $150~200.
이 숫자가 "Astra + 원본 수준 API"의 수치다. 에이전트가 받은 것은 텍스트 8.7 KB 한 번 — 인식 오라클(GT 6D 자세)·부품 크기·구멍 위치·도달성 판단·관절 상태 — 원본 CaP-X privileged 태스크가 주는 범위 안이다. 그 조건에서 gpt-6-astra는 30번 중 1번 조립했고, 같은 API로 사람이 짠 프로그램은 3번 중 2번 조립했다.
실패의 축이 옮겨갔다. 레시피가 있던 4차에선 "낙하 잔차 6.5 mm를 완료로 판정해 안 돌림"이 7/30이었는데, v5에선 "돌리다 넘어뜨림"이 16/30이다. 즉 에이전트는 나사식이라는 물리 사실에서 "잡고 돌린다"까지는 스스로 가지만, 강성 관절 제어에서 재파지·−90° 회전 중 다리를 유지하는 정밀도(재측정·보정 루프, 놓기 깊이)를 한 번의 생성으로 얻지 못한다.
단일 턴의 구조적 한계. 28/30이 자체 검사문으로 raise했다 — 프로그램이 문제를 감지는 하는데 관측을 받아 다시 계획할 통로가 없다. 원본 CaP-X가 multi-turn 설정을 따로 두는 이유가 이 데이터에 그대로 보인다.
30 trial로는 1 vs 3을 구분할 수 없다(p = 0.61). "누설 제거로 성능이 떨어졌다"는 결론도 이 규모에선 성립하지 않는다.
v5 병목에 정확히 대응: raise 대신 관측(다리 자세·기울기)을 프롬프트로 되돌려 재계획. 원본 *_multiturn.yaml 설정을 furniture 태스크에 붙이면 된다. 30 trial 15분이라 비용 부담 없음.
원본처럼 100 trial(≈50분)로 신뢰구간을 좁힌다. 현재 0.033의 95% CI는 대략 0.001~0.17.
§6 참조. 다시 돌릴 거면 텍스트 멀티턴만(30 trial ≈ $45), 놓기 깊이를 관측으로 잡을 수 있도록 MULTITURN_LIMIT을 20으로 늘리는 것이 가장 싼 다음 실험.
API cap-x/capx/integrations/franka/furniture_real.py · 태스크 capx/envs/tasks/franka/franka_furniture.py(FrankaFurnitureRealCodeEnv) · 설정 env_configs/furniture/franka_furniture_one_leg_real.yaml · 워커 FurnitureBench/scripts/capx/furniture_worker.py(--control joint), panda_fk.py · 실행 scripts/capx/eval_capx_furniture.sh one_leg gpt-6-astra 30 4 real, oracle_capx_furniture.sh · 출력 cap-x/outputs/capx_furniture/gpt-6-astra/one_leg_real_gpt-6-astra_260911_060525_118577/ · 로그 claude/260911/progress-capx_real_faithful_api.md
CaP-X의 상위 tier를 v5 API 위에 그대로 붙였다: 코드 블록 실행 → 실행 코드 + stdout/stderr(+현재 이미지, +턴 전후 차분을 VLM이 텍스트로) → REGENERATE(같은 에피소드에서 이어 실행) / FINISH, 최대 10턴. Agent0는 여기에 같은 모델 9회 샘플·종합 앙상블과 기하 헬퍼 스킬 라이브러리를 더한다. 프롬프트는 v5 물리 사실 프롬프트에 "실행 후 stdout/stderr를 받고 새 코드를 쓸 수 있다" 한 문장만 추가.
| 설정 | 피드백 | trial (job) | 성공 | trial당 LLM 호출 | 비고 |
|---|---|---|---|---|---|
| 단일 턴 v5 (기준) | 없음 | 30 (118577) | 1/30 = 0.033 | 1 | 완주 |
| real_multiturn | 코드 + stdout/stderr | 22 (118733, 선점) + 22 (118779, 취소) | 2/44 = 0.045 | ≈ 11 | 성공 2건 모두 own run: trial 10(12블록), trial 14(8블록 후 FINISH). 대부분 trial이 10턴 한계까지 재생성 |
| real_multiturn_vdm | + 앞/손목 이미지 + VLM 차분 설명 | 15 (118734, 선점) + 15 (118780, 취소) | 0/30 | ≈ 24 | — |
| real_agent0 | VDM + 앙상블 9× + 스킬 라이브러리 | 7 (118735, 취소) | 0/7 | ≈ 190 | trial당 ≈ $20, 3 trial에 40분 → 사용자 결정으로 중단 |
VLM_MODELS에 gpt-6-astra 등록, trial 타임아웃 1000 s를 CAPX_TRIAL_TIMEOUT env로, 결정 파서가 키워드 없이 코드 펜스만 온 응답을 FINISH로 오판하던 것을 REGENERATE로.