ClawOps Python SDK
ClawOps Voice API의 공식 Python 라이브러리입니다.
설치
# REST API SDK만 사용
pip install clawops
# AI Agent 포함
pip install clawops[agent]
# 특정 프로바이더 포함
pip install clawops[agent,openai,deepgram,elevenlabs,mcp]
# 전체 설치
pip install clawops[agent-all]
AI Agent (음성 에이전트)
ClawOpsAgent를 사용하면 한 줄로 인바운드 전화를 AI로 처리할 수 있습니다. ngrok 없이 WebSocket 역방향 연결로 동작합니다.
from clawops.agent import ClawOpsAgent, OpenAIRealtime
import asyncio
agent = ClawOpsAgent(
from_="07012341234",
session=OpenAIRealtime(
system_prompt="친절한 상담원입니다. 고객의 질문에 답변해주세요.",
voice="marin",
language="ko",
),
)
@agent.tool
async def check_order(order_id: str) -> str:
"""주문 상태를 확인합니다."""
return "배송 완료"
@agent.on("call_start")
async def on_start(call):
print(f"통화 시작: {call.from_number} -> {call.to_number}")
asyncio.run(agent.serve()) # Ctrl+C로 종료
Outbound 발신 Prewarm (낮은 첫 음성 latency)
outbound 통화에서 상대 응답 직후 첫 음성까지의 지연을 줄이기 위해, ClawOpsAgent 는
control WS 의 call.outbound_ready 이벤트 수신 즉시 LLM WebSocket 을 미리 연결하고
첫 audio delta 를 메모리에 누적한다 (prewarm + first-audio prebuffer). media WS 가 연결되면
누적된 chunk 를 flush 하여 사용자가 첫 음성을 빠르게 듣게 한다.
agent = ClawOpsAgent(
from_="07012341234",
session=OpenAIRealtime(system_prompt="..."),
prewarm_enabled=True, # default True
)
비용/효과 검증 단계에서는 prewarm_enabled=False 로 비활성화 가능하다.
측정 스크립트: scripts/measure_prewarm_cost.py (PREWARM-T 로그 파싱).
한계 / 비목표
session=은 동시 통화 1건까지 — 그 객체를 모든 통화가 공유하므로 두 번째 통화가 첫 통화의 대화 이력과 오디오 경로를 덮어쓴다. 동시 통화가 있으면session_factory=를 쓴다 (아래 "동시 통화" 참고).- Session 타입별 효과 차이 — Realtime (OpenAI / Gemini) 에서 LLM WS handshake +
session.update 가 prewarm 으로 숨겨지므로 latency 절감 효과가 가장 크다. 반면
PipelineSession은 STT / LLM / TTS 가 lazy 연결되므로, prewarm 단계에서는 STT 루프 기동과 greeting kickoff 정도만 선행되어 latency 절감 효과가 제한적이다.
동시 통화
한 프로세스가 통화 여러 건을 동시에 받으려면 session 대신 session_factory 를 준다.
ClawOps 가 통화마다 팩토리를 호출해 새 세션을 만들고, 대화 이력·오디오 큐·전송 대상이
그 통화 안에 갇힌다.
agent = ClawOpsAgent(
from_="07012341234",
session_factory=lambda: OpenAIRealtime(system_prompt="..."),
)
session= 을 주면 예전과 똑같이 동작한다 — 모든 통화가 같은 객체를 공유하므로
동시 통화 1건까지만 안전하다. 두 번째 통화가 시작되면 원인을 지목하는 에러 로그가 남는다.
동시 통화 한도 자체는 계정 요금제가 정한다(Individual 1 · Business 10). SDK 쪽 격리와는 별개이므로, 회선을 여러 개 운영하더라도 프로세스를 나눌 필요는 없다.
Call Transfer (통화 전환)
AI가 통화 중 다른 번호로 전환할 수 있습니다. Blind(즉시)와 Warm(안내 후) 모드를 지원합니다.
from clawops.agent import ClawOpsAgent, OpenAIRealtime, BuiltinTool
agent = ClawOpsAgent(
from_="07012341234",
session=OpenAIRealtime(
system_prompt="고객 문의를 처리하고, 필요하면 상담원에게 전환하세요.",
),
builtin_tools=[BuiltinTool.HANG_UP, BuiltinTool.TRANSFER_CALL],
)
# 코드에서 직접 전환도 가능
@agent.on("call_start")
async def on_start(call):
# 조건에 따라 즉시 전환
if should_transfer:
await call.transfer("01012345678", mode="warm", whisper="VIP 고객입니다.")
MCP 서버 연동
MCP 서버를 연결하여 AI에게 외부 도구를 제공할 수 있습니다.
pip install clawops[mcp]
from clawops.agent import ClawOpsAgent, OpenAIRealtime
from clawops.agent.mcp import MCPServerStdio, MCPServerHTTP
import asyncio
agent = ClawOpsAgent(
from_="07012341234",
session=OpenAIRealtime(
system_prompt="상담원입니다.",
),
mcp_servers=[
MCPServerStdio("npx", args=["@modelcontextprotocol/server-google"], env={"GOOGLE_API_KEY": "..."}),
MCPServerHTTP("https://my-mcp-server.com", headers={"Authorization": "Bearer token"}),
],
)
asyncio.run(agent.serve()) # Ctrl+C로 종료
MCP 서버는 전화가 올 때마다 자동으로 시작되고, 통화 종료 시 정리됩니다. MCP 서버가 제공하는 도구는 @agent.tool로 등록한 도구와 함께 세션에 자동 등록됩니다.
디버그 로깅
Agent의 내부 동작 (MCP 연결, 도구 등록, 도구 호출 등)을 확인하려면 로깅 레벨을 DEBUG로 설정하세요.
import logging
logging.basicConfig(level=logging.DEBUG)
# 또는 clawops.agent 로거만 DEBUG로:
logging.getLogger("clawops.agent").setLevel(logging.DEBUG)
OpenTelemetry Tracing
통화 흐름, MCP 도구 호출, LLM 세션을 OpenTelemetry로 추적할 수 있습니다.
pip install clawops[tracing]
# + 원하는 exporter
pip install opentelemetry-sdk opentelemetry-exporter-otlp
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
provider = TracerProvider()
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
trace.set_tracer_provider(provider)
from clawops.agent.tracing import TracingConfig
agent = ClawOpsAgent(
...,
tracing=TracingConfig(),
)
Span 계층:
call→mcp.connect→llm.session→tool.call→mcp.call_tool
자세한 사용법은 Agent 문서 를 참고하세요. (Tool, 이벤트, 통화 녹음, 파이프라인 모드, 커스텀 제공자, MCP 연동, Tracing 등)
REST API 사용법
from clawops import ClawOps
client = ClawOps(
api_key="sk_...", # 또는 CLAWOPS_API_KEY 환경변수 사용
account_id="AC1a2b3c4d", # 또는 CLAWOPS_ACCOUNT_ID 환경변수 사용
)
통화 (Calls)
# 발신 전화 생성
call = client.calls.create(
to="01012345678",
from_="07052358010",
url="https://my-app.com/twiml",
status_callback="https://my-app.com/status",
status_callback_event="initiated ringing answered completed",
)
print(call.call_id)
# 음성사서함 감지(AMD) — Enable=결과만 통보(통화 계속), Hangup=사서함이면 자동 종료
amd_call = client.calls.create(
to="01012345678",
from_="07052358010",
url="https://my-app.com/twiml",
machine_detection="Enable",
)
# 통화 종료 후 결과 확인: human(사람) / machine(자동응답기) / unknown(판정 불가)
print(client.calls.get(amd_call.call_id).answered_by)
# status_callback 을 설정했다면 completed 이벤트 payload 의 AnsweredBy 로도 통보됩니다.
# 통화 목록 조회 (페이지네이션)
page = client.calls.list(status="completed", page=0, page_size=20)
for call in page:
print(call.call_id, call.status)
# 모든 통화를 자동으로 순회
for call in client.calls.list().auto_paging_iter():
print(call.call_id)
# 특정 통화 조회
call = client.calls.get("CAabcdef1234567890")
# 연결 실패 사유 확인 — status="failed" 는 결번·망 오류·시스템 오류를 모두 포함하는
# 대분류라, 다시 걸어도 소용없는 번호를 가려내려면 hangup_cause 를 봅니다.
DO_NOT_RETRY = {"invalid_number", "number_changed", "incompatible_destination"}
if call.status != "completed":
if call.hangup_cause in DO_NOT_RETRY:
print(f"결번 — 목록에서 제외: {call.to}") # hangup_cause_q850=1, sip_response_code=404
elif call.hangup_source in ("app", "system"):
print("ClawOps 측 오류 — 재시도")
else:
print(f"일시적 사유({call.hangup_cause}) — 나중에 재시도")
# 통화 종료
call = client.calls.update("CAabcdef1234567890", status="completed")
# 통화 전사 상태 조회 (completed 시 segments 까지 inline)
state = client.calls.get_transcript("CAabcdef1234567890")
if state.status == "completed":
for seg in state.segments or []:
print(f"[{seg.speaker}] {seg.text}")
elif state.status == "not_requested":
# 조직 설정 off 거나 아직 요청 안 된 상태 — 명시 요청 (사용량 과금)
client.calls.request_transcript("CAabcdef1234567890")
# 통화 요약 상태 조회 (completed 시 result_json 까지 inline)
summary = client.calls.get_summary("CAabcdef1234567890")
if summary.status == "completed":
print(summary.result_json) # {"coreSummary": ..., "decisions": [...], ...}
통화 녹음 (Recordings)
콘솔에서 들리는 것과 동일한 서버측 MixMonitor 원본(WAV PCM 16bit mono 8kHz)을 다운로드합니다. SDK 측 mix.wav 가 아닌 서버에서 합성된 파일이라 싱크/볼륨이 정상입니다.
# call list 응답의 recording_url 필드로 녹음 유무 확인 가능
page = client.calls.list(page_size=10)
for call in page.data:
if not call.recording_url: # failed/no-answer 등은 None
continue
rec = client.recordings.download(call.call_id)
with open(rec.filename or f"{call.call_id}.wav", "wb") as f:
f.write(rec.data)
print(rec.content_type, len(rec.data), "bytes")
녹음이 없는 통화(recording_url is None)에 호출하면 NotFoundError(404) 가 발생합니다.
# 녹음 삭제 (멱등 — 이미 없어도 성공)
client.recordings.delete("CAabcdef1234567890")
전화번호 (Numbers)
# 번호 발급 — 풀에서 자동 배정되며 어떤 번호가 나올지는 지정할 수 없다
number = client.numbers.create()
print(number.number, number.routing_type) # 07012340001 webhook
# 번호 목록 조회 (페이지네이션 없음 — 보유한 번호가 전부 반환된다)
numbers = client.numbers.list()
# 발급 직후에는 webhook_url 이 비어 있어 걸려온 전화가 거절된다. 착신 라우팅을 지정한다.
# 매니지드 에이전트가 받도록
client.numbers.update("07012340001", routing_type="agent", agent_id="AG7c2f9b1e4a6d")
# 콜 플로우(ARS)가 받도록
client.numbers.update("07012340001", routing_type="callflow", call_flow_id="CF41b8e07d9c25")
# 내 서버의 VoiceML 이 받도록
client.numbers.update(
"07012340001",
routing_type="webhook",
webhook_url="https://my-app.com/voice",
)
# 보유한 다른 번호로 착신전환 (같은 계정의 번호만 가능)
client.numbers.update("07012340001", routing_type="forward", forward_to="07012340002")
# 소프트폰 단말 착신 (sip_trunk 부가서비스 + 등록 단말 필요)
creds = client.sip_credentials.list(status="active")
client.numbers.update("07012340001", routing_type="softphone", sip_credential_id=creds[0].id)
# 외부 PBX 로 (sip_trunk 부가서비스 + 활성 라우트 1개 이상 필요)
endpoints = client.sip_endpoints.list(status="active")
client.numbers.update("07012340001", routing_type="sip", sip_endpoint_id=endpoints[0].id)
# 수신 통화 상태 통지
client.numbers.update(
"07012340001",
status_callback="https://my-app.com/call-status",
status_callback_events="initiated ringing answered completed",
)
# 번호 반납 — 되돌릴 수 없고 같은 번호를 다시 받는다는 보장이 없다
client.numbers.delete("07012340001")
라우팅을 바꾸면 다른 라우팅 필드는 서버에서 자동으로 비워집니다. agent 에서 webhook 으로
되돌리면 agent_id 가 None 이 되므로, 다시 agent 로 돌아갈 때 agent_id 를 새로 지정해야
합니다.
메시지 (Messages)
# SMS 발송
msg = client.messages.create(
to="01012345678",
from_="07052358010",
body="안녕하세요",
)
print(msg.message_id)
# MMS 발송
msg = client.messages.create(
to="01012345678",
from_="07052358010",
body="사진 첨부",
type="mms",
subject="제목",
)
# LMS (장문 문자) 발송
message = client.messages.create(
to="01012345678",
from_="07052358010",
body="긴 내용의 메시지입니다...",
type="lms",
subject="알림",
)
# 메시지 목록 조회 (필터링)
page = client.messages.list(type="sms", status="sent", page=0, page_size=20)
# 번호로 필터 (하이픈 유무를 모두 매칭), 알림톡만 골라 보기
page = client.messages.list(number="07052358010")
page = client.messages.list(type="ata")
for msg in page:
print(msg.message_id, msg.status)
# 모든 메시지를 자동으로 순회
for msg in client.messages.list().auto_paging_iter():
print(msg.message_id)
# 특정 메시지 조회
msg = client.messages.get("MG0123456789abcdef")
카카오 알림톡 (Kakao)
알림톡은 검수된 템플릿으로만 나갑니다. 본문을 요청으로 쓰는 게 아니라, 승인된 템플릿에 변수를 채워 보냅니다.
# 1. 연결된 채널 찾기
channels = client.kakao.channels.list(status="connected")
channel = channels.data[0]
# 2. 그 채널의 템플릿 중 발송 가능한 것 고르기
# ⚠️ status("APPROVED")가 아니라 sendable 이 정본입니다 — 휴면 여부까지 반영된 값입니다.
templates = client.kakao.templates.list(channel_id=channel.id)
template = next(t for t in templates if t.sendable)
print(template.variables) # ['#{고객명}'] — 이 목록을 전부 채워야 합니다
# 3. 발송
msg = client.messages.create(
to="01012345678",
from_="07052358010",
kakao={
"channel_id": channel.id,
"template_id": template.id,
"variables": {"고객명": "홍길동"}, # '고객명' 과 '#{고객명}' 둘 다 받습니다
},
fallback={"body": "주문이 접수되었습니다."},
)
print(msg.type, msg.body) # ata 홍길동님, 주문이 접수되었습니다.
body와kakao는 섞을 수 없습니다.create()가 오버로드로 갈려 있어 타입 단계에서 걸리고, 타입체커를 쓰지 않아도 요청을 보내기 전에TypeError로 거절합니다.- 버튼·아이템 리스트·강조 문구는 템플릿에 검수된 대로 나갑니다. 요청으로 바꿀 수 없고, 응답
body에도 담기지 않습니다. fallback은 별도 메시지 1건입니다. 알림톡이 실패하면(수신자가 카카오톡을 쓰지 않는 등) 이 문구가 문자로 나가고 문자 단가로 따로 청구됩니다. 생략하면 템플릿 본문을 그대로 문자로 보냅니다. 아예 보내지 않으려면fallback={"disabled": True}.
채널 연결
두 단계입니다. 서버는 그 사이 상태를 저장하지 않으므로(미완료 담당자 번호를 남기지 않기 위해) search_id 와 phone_number 를 두 번 모두 보냅니다.
# 업종 카테고리는 열린 집합입니다 — 하드코딩하지 말고 이 응답을 선택지로 쓰세요.
categories = client.kakao.channel_categories()
# ① 담당자 휴대전화로 인증번호 발송 (응답에 인증번호는 없습니다 — 202)
req = client.kakao.channels.request_token(search_id="example", phone_number="010-1234-5678")
print(req.phone_number_masked, req.retry_after_seconds)
# ② 받은 인증번호로 연결 완료
channel = client.kakao.channels.connect(
search_id=req.search_id,
phone_number="010-1234-5678",
category_code=categories.data[0].code,
token="394812",
)
connect는 멱등이지만, 타임아웃되면 다시 보내지 마세요. 이미 연결에 성공했을 수 있습니다.client.kakao.channels.retrieve(channel_id)로 확인하세요 — 이 조회는 몇 번을 불러도 안전합니다.- 연결에 실패해도 인증번호는 소모됩니다 (
KAKAO_TOKEN_INVALID·KAKAO_CHANNEL_REJECTED둘 다). 원인을 해결한 뒤 새로 요청해야 합니다. 다만429/503은 연결이 시도되지 않았다는 뜻이라 인증번호가 아직 유효합니다. list()는 카카오 쪽 상태를 확인하지 않습니다. 저장된 연결 정보를 그대로 돌려주므로 빠릅니다. 실제 상태 확인은retrieve()뿐이고, 그 호출이status를 갱신합니다.
# 연결 해제 — ⚠️ 되돌릴 수 없고, 그 채널의 알림톡 템플릿도 함께 삭제됩니다.
# 템플릿은 카카오 검수를 다시 받아야 하므로 복구에 시간이 걸립니다.
client.kakao.channels.disconnect(channel.id)
카카오 브랜드 메시지 (Brand)
채널을 추가한 친구에게 나가는 광고성 메시지입니다. 등록한 템플릿으로 보냅니다.
channels = client.kakao.channels.list(status="connected")
templates = client.kakao.brand_templates.list(channel_id=channels.data[0].id)
msg = client.messages.create(
to="01012345678",
from_="07052358010",
brand={
"channel_id": channels.data[0].id,
"template_id": templates.data[0].id,
"variables": {"고객명": "홍길동"},
},
)
print(msg.type) # 'bms'
⭐ 알림톡과 달리 검수가 없습니다 — brand_templates.list() 가 돌려준 템플릿은 전부 바로
쓸 수 있어 sendable 같은 칸이 없습니다. 알림톡 템플릿과는 다른 표라 templates 가 아닌
brand_templates 로 조회합니다.
알림톡과 갈리는 점 둘:
- 야간에 보낼 수 없습니다. 오후 8시 50분 ~ 다음 날 오전 8시(KST)는
422 kakao_brand_night_blocked입니다. 접수 전에 막으므로 예약되지 않습니다. - 대체발송이 없습니다. 카카오톡을 쓰지 않는 수신자에게 문자로 대신 나가지 않습니다.
fallback을 함께 주면 타입 에러이고, 타입체커를 쓰지 않아도TypeError로 거절합니다.
(광고) 표기와 수신거부 안내는 카카오가 붙이므로 본문에 넣지 마십시오.
⚠️ 단가가 알림톡보다 훨씬 높고 말풍선 유형에 따라 갈립니다 — 템플릿의 chat_bubble_type
이 그 축입니다. content 는 유형에 따라 None 일 수 있습니다(본문이 담기는 자리가 다릅니다).
자유형 — 템플릿 없이 보내기
template_id 대신 free 에 말풍선을 실으면 템플릿을 등록하지 않고 보냅니다. 종료된
친구톡을 대신하는 방식입니다.
client.messages.create(
to="01012345678",
from_="07052358010",
brand={
"channel_id": channel_id,
"free": {
"chatBubbleType": "TEXT",
"content": "이번 주 신메뉴가 나왔어요.",
"buttons": [{"name": "메뉴 보기", "linkType": "WL", "linkMobile": "https://example.com"}],
},
},
)
template_id 와 free 는 정확히 하나만 실을 수 있습니다 — 둘 다 주거나 둘 다 빼면
mypy 가 잡고, 타입을 쓰지 않으면 TypeError 입니다.
⛔ 자유형에는 변수를 쓸 수 없습니다. 치환해 줄 템플릿이 없어 #{이름} 이 그대로 톡에
렌더되므로 서버가 막습니다. 값을 채우려면 템플릿을 등록해 template_id 로 보내십시오.
⚠️ free 안쪽은 SDK 가 검사하지 않습니다. 말풍선 규격표는 서버에 한 벌만 두는 것이
의도입니다 — SDK 가 사본을 들면 카카오가 칸을 늘린 날 SDK 가 조용히 깎습니다.
⚠️ 검수를 거치지 않으므로 여기 실은 버튼 링크가 그대로 톡에 렌더됩니다.
자유형에 이미지 넣기
image = client.kakao.brand_images.upload(
file=open("banner.png", "rb").read(),
filename="banner.png",
bubble_type="WIDE",
)
client.messages.create(
to="01012345678",
from_="07052358010",
brand={
"channel_id": channel_id,
"free": {"chatBubbleType": "WIDE", "content": "신메뉴가 나왔어요.", "imageId": image.id},
},
)
한 번 올린 이미지는 여러 발송에 재사용합니다. ⚠️ 규격이 말풍선 유형마다 달라, 올릴 때 준
bubble_type 과 다른 유형에 쓰면 카카오가 발송 단계에서 거절합니다. 와이드리스트형의 작은
항목만 slot="sub" 를 씁니다.
id 를 잃었으면 client.kakao.brand_images.list() 로 되찾습니다.
멀티 계정 접근
# 다른 계정의 리소스에 접근
other = client.accounts("AC_other_account_id")
other.calls.list()
other.numbers.list()
other.messages.list()
비동기 사용법
from clawops import AsyncClawOps
# async context manager 사용
async with AsyncClawOps(api_key="sk_...", account_id="AC1a2b3c4d") as client:
call = await client.calls.create(
to="01012345678",
from_="07052358010",
url="https://my-app.com/twiml",
)
print(call.call_id)
# 모든 리소스 메서드는 비동기 버전을 제공합니다
page = await client.calls.list(status="completed")
async for call in page.auto_paging_iter():
print(call.call_id)
# 메시지 발송
msg = await client.messages.create(
to="01012345678", from_="07052358010", body="안녕하세요",
)
웹훅 서명 검증
client.webhooks.verify(
url="https://my-app.com/webhook",
params={"CallId": "CA...", "CallStatus": "completed"},
signature=request.headers["X-Signature"],
signing_key="your_account_signing_key",
)
서명이 유효하지 않으면 WebhookVerificationError가 발생합니다.
에러 처리
from clawops import ClawOps, BadRequestError, AuthenticationError, NotFoundError
client = ClawOps()
try:
call = client.calls.create(to="01012345678", from_="07052358010", url="https://...")
except BadRequestError as e:
print(f"잘못된 요청: {e.status_code} - {e.body}")
except AuthenticationError as e:
print(f"유효하지 않은 API 키: {e.status_code}")
except NotFoundError as e:
print(f"리소스를 찾을 수 없음: {e.status_code}")
모든 에러는 ClawOpsError를 상속합니다. HTTP 에러는 status_code, code, response, body, request 속성을 제공합니다.
사유는 code 로 분기하세요
같은 상태 코드에 여러 사유가 몰립니다 — 422 만 해도 수신거부·할당량 초과·템플릿 미승인이 섞입니다. 메시지 문구는 바뀌지만 code 는 계약입니다.
from clawops import BadRequestError, UnprocessableEntityError
try:
client.messages.create(to=..., from_=..., kakao={...})
except BadRequestError as e:
if e.code == "kakao_variable_missing":
... # 템플릿이 요구하는 변수를 덜 채웠다
elif e.code == "kakao_variable_unknown":
... # 템플릿에 없는 변수를 보냈다
except UnprocessableEntityError as e:
if e.code == "recipient_blocked":
... # 수신거부 명단에 있다 (발송 자체가 일어나지 않아 이력에도 남지 않는다)
elif e.code == "quota_exceeded":
...
⚠️ 서버가 도메인마다 표기를 달리 씁니다 — 문자 도메인은 snake_case(kakao_variable_missing), 채널 연동은 SCREAMING_CASE(KAKAO_TOKEN_INVALID)입니다. SDK 는 실제 응답과 어긋나지 않도록 섞인 채로 노출합니다. 서버가 코드를 새로 만들어도 그대로 실립니다.
| 에러 | 상태 코드 |
|---|---|
BadRequestError |
400 |
AuthenticationError |
401 |
PermissionDeniedError |
403 |
NotFoundError |
404 |
ConflictError |
409 |
UnprocessableEntityError |
422 |
InternalServerError |
500+ |
ServiceUnavailableError |
503 |
설정
재시도
기본적으로 408, 409, 429, 500+ 에러 시 지수 백오프로 최대 2회 재시도합니다.
client = ClawOps(max_retries=5)
# 재시도 비활성화
client = ClawOps(max_retries=0)
타임아웃
기본 타임아웃은 600초 (연결 타임아웃 5초)입니다. 클라이언트 또는 요청 단위로 변경할 수 있습니다:
# 클라이언트 단위
client = ClawOps(timeout=30.0)
# 요청 단위
call = client.calls.create(..., timeout=10.0)
커스텀 HTTP 클라이언트
프록시, 커스텀 인증서 등 고급 설정이 필요한 경우 httpx.Client를 직접 주입할 수 있습니다:
import httpx
client = ClawOps(
http_client=httpx.Client(proxies="http://proxy.example.com:8080"),
)
환경변수
| 변수 | 설명 | 필수 여부 |
|---|---|---|
CLAWOPS_API_KEY |
API 키 (sk_...) |
예 (생성자에 전달하지 않은 경우) |
CLAWOPS_ACCOUNT_ID |
기본 계정 ID (AC...) |
예 (생성자에 전달하지 않은 경우) |
CLAWOPS_BASE_URL |
API 기본 URL | 아니오 (기본값: https://api.claw-ops.com) |
OPENAI_API_KEY |
OpenAI API 키 | OpenAI Realtime 사용 시 |
GOOGLE_API_KEY |
Google API 키 | Gemini Realtime 사용 시 |
문서
- AI Agent 가이드 — 음성 에이전트 상세 사용법, 파이프라인 모드, 커스텀 제공자, MCP 연동
- 트러블슈팅 — SSL 인증서 에러, 연결 실패 등 문제 해결
다른 언어
| 언어 | 패키지 | 저장소 |
|---|---|---|
| Node.js / TypeScript | @teamlearners/clawops |
sdk-node |
요구사항
- Python 3.9+
httpx>= 0.23.0pydantic>= 2.0.0aiohttp>= 3.9.0 (Agent 사용 시)
라이선스
Apache-2.0
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file clawops-0.51.0.tar.gz.
File metadata
- Download URL: clawops-0.51.0.tar.gz
- Upload date:
- Size: 256.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
35450b973a99c5179c23085f7c841944b916ed2664115dec385e1fa029b5fde7
|
|
| MD5 |
ebbf04c37ff5f09e3f2b1535f4af0d13
|
|
| BLAKE2b-256 |
1f781745321403875c760030fa84df62f935216f56f408167f834df1c4a12eb8
|
Provenance
The following attestation bundles were made for clawops-0.51.0.tar.gz:
Publisher:
publish.yml on clawopshq/sdk-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
clawops-0.51.0.tar.gz -
Subject digest:
35450b973a99c5179c23085f7c841944b916ed2664115dec385e1fa029b5fde7 - Sigstore transparency entry: 2737425385
- Sigstore integration time:
-
Permalink:
clawopshq/sdk-python@85427983b1259406c61fc6cee85f1ea3a0980fd5 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/clawopshq
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@85427983b1259406c61fc6cee85f1ea3a0980fd5 -
Trigger Event:
push
-
Statement type:
File details
Details for the file clawops-0.51.0-py3-none-any.whl.
File metadata
- Download URL: clawops-0.51.0-py3-none-any.whl
- Upload date:
- Size: 178.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5512f1b9d9b5a05d2f54d38bcb51275626691ce227d340d2616a89b01d5cc978
|
|
| MD5 |
96e628ddf675954ae1c3bbf31cb511b6
|
|
| BLAKE2b-256 |
1a78229a8a77bfb648bf0c9d94735606922da031272618f1f239546758b0ceb4
|
Provenance
The following attestation bundles were made for clawops-0.51.0-py3-none-any.whl:
Publisher:
publish.yml on clawopshq/sdk-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
clawops-0.51.0-py3-none-any.whl -
Subject digest:
5512f1b9d9b5a05d2f54d38bcb51275626691ce227d340d2616a89b01d5cc978 - Sigstore transparency entry: 2737425451
- Sigstore integration time:
-
Permalink:
clawopshq/sdk-python@85427983b1259406c61fc6cee85f1ea3a0980fd5 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/clawopshq
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@85427983b1259406c61fc6cee85f1ea3a0980fd5 -
Trigger Event:
push
-
Statement type: