콘텐츠로 이동

PhysiCar API

피지카 에이아이의 클라우드 AI 서비스를 내 코드에서 호출하는 방법입니다. 서비스는 둘입니다: chat(텍스트, POST https://api.physicar.ai/chat)과 realtime(음성, wss://api.physicar.ai/realtime). 둘 다 하나의 공통 규격으로 뒤에서 여러 프로바이더에 라우팅되고, 같은 프롬프트 형태 — 지시문 + 도구 정의 — 를 받으므로 하나의 "두뇌"로 텍스트 에이전트와 음성 에이전트를 함께 만들 수 있습니다.

완전한 동작 예제는 워크스페이스 노트북 examples/agent.ipynb(커널 Python 3 (PhysiCar AI))입니다: 두 에이전트를 브라우저에서 실행하고 tool call로 로봇을 움직이는 MyApp 페이지.

인증

  • 세션 토큰으로 인증합니다. HTTP 요청은 Authorization: Bearer <token> 헤더로, WebSocket은 서브프로토콜 token.<토큰>으로 전달합니다(브라우저는 WS에 헤더를 실을 수 없습니다).
  • 모든 /myapp/ 페이지에는 nginx가 physicarSession.token()을 자동 주입합니다 — 로그인한 사용자의 토큰을 반환하며, 설정 코드가 필요 없습니다. 반드시 요청마다 읽으세요: 공유 로봇에서 토큰 하나를 전역에 캐시하면 모든 사용자가 한 계정으로 묶입니다.
  • /chat은 게스트 요청(토큰 없음)도 네트워크당 소량의 일일 한도 안에서 허용합니다. 로그인 사용량은 크레딧에서 차감됩니다.

Chat

POST https://api.physicar.ai/chat — 턴당 요청 하나. 프롬프트(모델 + 지시문 + 도구)는 매 요청에 인라인으로 보내고, 서버는 대화만 보관합니다 — chat_id/turn으로 지정하며 7일간 유지됩니다.

GET /chat/models가 사용 가능한 모델 목록을 1M 토큰당 가격과 함께 반환합니다 (교실 학생이라면 교사의 허용 여부도 함께).

요청 본문

{
  "user_message": { "contents": [ { "type": "text", "text": "안녕!" } ] },
  "prompt":  { "model": "...", "instructions": "...", "tools": [ ... ] },
  "stream":  true,
  "audio":   false,
  "chat_id": "…",
  "turn":    0
}
  • user_message.contentstext 및/또는 image ({ "type": "image", "mime": "image/jpeg|image/png", "base64": "…" }) 파트.
  • user_message.tool_call_outputs — 직전 턴 tool call들의 실행 결과(아래 도구 루프 참고).
  • promptmodel, instructions, tools, reasoning_effort, max_completion_tokens. max_completion_tokens는 서버가 잔액이 감당하는 만큼으로 상한을 겁니다. 프롬프트는 서버에 저장해 두고(/chat/prompt CRUD) prompt_id로 참조할 수도 있습니다.
  • chat_id + turn — 기존 대화 이어가기. 첫 턴에는 둘 다 생략하면 done 이벤트가 발급된 chat_id를 돌려줍니다. 다음에는 turn + 1을 보내거나 turn을 생략하면 끝에서 이어집니다. 이전 turn을 보내면 대화가 그 지점으로 되감깁니다.

도구 정의

{
  "name": "drive",
  "description": "Drive the robot.",
  "properties": [
    { "name": "speed", "type": "number", "description": "m/s", "required": false }
  ]
}

스트림 이벤트

"stream": true면 응답은 Server-Sent Events입니다; 각 data: 라인은 type을 가진 JSON 객체입니다:

type 담는 내용
text 답변 조각(content)
tool_call call_id, name, arguments
audio data(base64 PCM) — "audio": true일 때만
done chat_id, turn, full_text, tool_calls, usage, finish_reason
error message, code

usage는 그 턴에 차감된 크레딧입니다. finish_reason은 프로바이더 중립 값입니다: stop(정상 — tool call 포함), length(잘림), refusal(거절).

stream 없이 호출하면 같은 필드를 담은 JSON 객체 하나가 돌아옵니다(chat_id, turn, text, tool_calls, usage, finish_reason, 그리고 요청했다면 {data, duration_ms} 형태의 audio).

도구 루프

done.tool_calls가 비어 있지 않으면 각 {call_id, name, arguments}를 직접 실행하고 결과를 다음 요청의 user_message로 보냅니다:

{
  "chat_id": "…", "turn": 1,
  "prompt": { "model": "...", "instructions": "...", "tools": [ ... ] },
  "user_message": {
    "contents": [],
    "tool_call_outputs": [
      { "call_id": "…", "name": "drive",
        "contents": [ { "type": "text", "text": "speed 0.5 m/s, drove 2 s" } ] }
    ]
  },
  "stream": true
}

도구 결과의 contents에는 이미지도 넣을 수 있습니다 — 카메라 도구가 찍은 사진을 대화에 넣어 모델이 묘사하게 만드는 방법이 바로 이것입니다.

브라우저에서

const res = await fetch("https://api.physicar.ai/chat", {
  method: "POST",
  headers: { "Content-Type": "application/json",
             "Authorization": "Bearer " + physicarSession.token() },
  body: JSON.stringify({
    chat_id: chatId,            // 첫 턴에는 undefined
    turn,                       // 첫 턴에는 0
    prompt: { model, instructions, tools },
    user_message: { contents: [{ type: "text", text: "안녕!" }] },
    stream: true,
  }),
});

Realtime

wss://api.physicar.ai/realtime — speech-to-speech 음성 대화:

마이크 ──▶ realtime 클라우드 (speech-to-speech LLM) ──▶ 스피커
                  │            ▲
             tool_call    tool_result
                  ▼            │
            내 도구들 ──▶ 로봇 Web API (/speed, /camera, ...)

GET https://api.physicar.ai/realtime/models가 사용 가능한 realtime 모델 목록을 반환합니다. 브라우저는 token.<토큰> 서브프로토콜로 인증하고, 브라우저가 아닌 클라이언트는 ?token=도 쓸 수 있습니다(쿼리 문자열은 로그에 남을 수 있어 서브프로토콜 방식을 권장합니다).

프로토콜은 JSON 텍스트 프레임만 사용합니다 — 오디오는 base64 pcm16으로 JSON에 실려 오갑니다. 샘플레이트는 모델마다 달라 session.readyaudio_config로 알려줍니다.

클라이언트 → 서버

이벤트 담는 내용
session.start prompt(model, instructions, tools, voice), 대화를 이어갈 때 선택적 chat_id
audio dataaudio_config.input_rate의 base64 pcm16
text text — 텍스트 입력, 턴을 트리거
image data, mime, turn_complete — 명시적 이미지 입력(예: 카메라 촬영)
tool_result call_id, name, output
session.end 세션 종료

서버 → 클라이언트

이벤트 담는 내용
session.ready model, audio_config(format, input_rate, output_rate), chat_id
audio.delta dataaudio_config.output_rate의 base64 pcm16
transcript.delta role(user/assistant), text
tool_call call_id, name, arguments — chat과 같은 형태
turn.complete 턴 완료
interrupted 끼어들기(barge-in) — 재생 중인 오디오를 버리세요
error code, message
session.end code, reason, session_cost(크레딧)

세션

  • 세션은 최대 30분, 100 크레딧까지이고, 90초 동안 활동이 없으면 종료됩니다. session.end 이벤트가 사유 코드(CLIENT_END, TIME_LIMIT, CREDIT_LIMIT, IDLE_TIMEOUT 등)와 세션이 소비한 크레딧을 알려줍니다. 사용자당 동시 세션은 최대 10개입니다.
  • 전사(transcript)는 /chat과 대화 저장소를 공유합니다: 세션의 chat_id를 이후의 session.start에 — 또는 /chat 요청에 — 넘기면 대화가 이어집니다.

브라우저에서

const ws = new WebSocket("wss://api.physicar.ai/realtime",
                         ["token." + physicarSession.token()]);
ws.onopen = () => ws.send(JSON.stringify({
  type: "session.start",
  prompt: { instructions, tools },
}));

더 알아보기

AI