xsoft_comply Python SDK
AI 기본법 대응 거버넌스 플랫폼 xsoft_comply의 공식 Python SDK.
설치
0) 가상환경(venv) 먼저 — externally-managed-environment 에러 예방
Ubuntu 23.04+ · Debian 12+ · Fedora 38+ 등 최신 배포판의 시스템 파이썬은 PEP 668 로 잠겨 있어
pip install 이 다음 메시지로 멈춥니다.
error: externally-managed-environment
× This environment is externally managed
시스템 파이썬을 건드리지 말고 가상환경에 설치하세요(권장).
# 1) venv 만들기 (python3-venv 가 없다면: sudo apt install python3-venv)
python3 -m venv ~/venv
# 2) venv 의 pip 으로 설치 — activate 없이 경로로 직접 불러도 됩니다
~/venv/bin/pip install --upgrade pip
# 3) 실행도 같은 venv 로
~/venv/bin/python -c "import xsoft_comply; print(xsoft_comply.__version__)"
셸에 붙여 쓰려면 source ~/venv/bin/activate 후 pip·python 을 그대로 쓰면 됩니다
(빠져나올 때는 deactivate).
venv 를 만들 수 없는 환경(컨테이너 베이스 이미지 등)에서만 마지막 수단으로
pip install --break-system-packages ...를 씁니다. 시스템 패키지와 충돌할 수 있어 권장하지 않습니다.
1) 설치 방법 — PyPI 정식 배포 (표준)
xsoft-comply 는 PyPI 에 정식 배포되어 있습니다. 아래가 표준 설치 경로입니다.
현재 PyPI 게시본 =
0.1.0(2026-08-20 업로드). 저장소의 소스 버전은0.1.1이며 아직 PyPI 에 올리지 않았습니다(빌드·해시 산출까지만 완료 · 2026-08-22).pip install xsoft-comply는 당분간0.1.0을 설치합니다.0.1.1이 게시되면 이 문단과 아래 해시를 함께 갱신합니다.
~/venv/bin/pip install xsoft-comply
소스에서 직접(개발·수정 목적) 설치하려면:
# 저장소 루트에서
~/venv/bin/pip install -e sdk/python
★ 안전하게 설치하기 (권장)
이 SDK 는 고객사 서버 안에서 실행됩니다. 공급망 공격(타이포스쿼팅·계정 탈취)을 막으려면 이름을 정확히 쓰고 해시를 고정하시기 바랍니다.
# ① 정확한 배포 이름 = xsoft-comply (import 이름은 xsoft_comply)
# 비슷한 이름(xsoftcomply · xsoft-compliance 등)은 당사 배포본이 아닙니다.
# ② 해시 고정 = 아래 예시대로 requirements.txt 파일을 직접 만들어 적어두고
# --require-hashes 로 설치합니다 (이 파일은 패키지에 동봉되지 않습니다).
~/venv/bin/pip install --require-hashes -r requirements.txt
아래 내용을 담은 requirements.txt 를 직접 생성합니다(해시는 릴리스 노트의 값을 그대로 복사):
# 아래 두 해시는 PyPI 게시본(0.1.0)의 실측 sha256 입니다.
# xsoft_comply-0.1.0-py3-none-any.whl / xsoft_comply-0.1.0.tar.gz
xsoft-comply==0.1.0 \
--hash=sha256:7c73e5c82f9878808eab4fec6884d7ab02d42bf0bd04de59e085d92cbd66b345 \
--hash=sha256:02670111fe205597bb8d6e76626ac3442b1ecfd6d342381a597b2176d8b5f430
0.1.1 (아직 미게시) 의 빌드 아티팩트 sha256 은 RELEASES.md 에 적어 둡니다.
이 README 자체가 배포 아티팩트 안에 들어가므로(long_description) 여기에 자기 해시를 적으면
적는 순간 그 해시가 틀려집니다 — 그래서 해시 기록은 패키지 밖 파일에 둡니다.
0.1.1 변경분 = 버전 번호와 문서·주석 정정뿐입니다. 게시본 0.1.0 의 패키지 코드와 대조한 결과
실행되는 코드의 차이는 __version__ 한 줄이며, 나머지는 전부 주석·독스트링입니다
(파일 목록 동일). 공개 API·동작은 0.1.0 과 같습니다 — occurred_at·event_id 는
언제나 SDK 가 스스로 만들며, 이를 바깥에서 덮어쓰는 파라미터는 배포 패키지에 없습니다.
설치 후 확인:
~/venv/bin/python -c "import xsoft_comply; print(xsoft_comply.__version__)"
우리가 지키는 것 — 외부 의존성 0(표준 라이브러리만), 설치 시 실행되는 코드 없음
(setup.py·.pth 없음). import 만으로는 네트워크를 열지 않습니다
(init() 을 불러야 전송 스레드가 뜹니다).
업로드 방식(사실 그대로) — 현재 게시본 0.1.0 은 PyPI 계정 2FA + twine(API 토큰) 수동 업로드로
올렸습니다. PyPI 게시 페이지의 "Uploaded using Trusted Publishing?" 은 No 입니다.
Trusted Publishing(OIDC) 전환은 아직 적용 전이며 예정 사항입니다
Trusted Publishing 전환은 예정 사항입니다. 적용되지 않은 보안장치를 적용된 것처럼 적지 않습니다.
외부 의존성 없음(표준 라이브러리만). Python 3.9 이상.
빠른 시작
import xsoft_comply as xc
xc.init(
api_key="xsk_live_...", # API 키 (대시보드에서 발급)
endpoint="https://your-domain.com/api/v1",
log_llm_content=False, # 기본값 · 원문 미전송
)
with xc.session(agent_id="support-bot") as s:
s.llm_call(
model="gpt-4o-mini",
provider="openai",
tokens_in=512,
tokens_out=128,
latency_ms=820,
)
s.tool_call(tool="crm.lookup", status="ok", latency_ms=12)
s.data_access(resource="customer_profile", scope="read", record_count=1)
s.decision(decision="escalate", options=3, confidence=0.72)
s.human_review(human_review="approved", reviewer_role="manager")
s.action(action="ticket.create", status="ok")
세션 컨텍스트를 벗어나면 session_end 이벤트가 자동으로 전송된다.
조항 증적 (제31~36조) · 2026-08-20
event_type 은 6종이다 — session_start · llm_call · tool_call · data_access ·
action · session_end.
decision() · human_review() 는 메서드로 그대로 남는다. 내부에서
action + 조항 증적(제34조 고영향 책무)으로 조립돼 나간다 → 기존 코드는 그대로 돌아간다.
바뀌는 것은 «어디에 적재되는가» 뿐이다(compliance_evidence + ev_high_impact 로 정규화 분배).
with xc.session(agent_id="loan-bot") as s:
# 한 이벤트에 여러 조항 증적을 담을 수 있다(배열)
s.action("loan.approve", status="ok", evidence=[
xc.evidence_block(34, supervisor_id="sup-1", decision_basis="심사 기준표 v3"),
xc.evidence_block(31, ai_generated_label="labeled", label_method="watermark",
output_id="out-99"),
])
# 런타임이 아닌 «선언» 증적도 같은 길로 보낸다(원천 우회 금지 = hash-chain 을 받는다)
s.evidence(36, source="declared", evidence_key="agent-2026",
company_country="US", has_korean_address="false",
domestic_agent_name="…", domestic_agent_contact="…")
evidence_key= 개발자 중복방지키. 같은 선언을 여러 번 보내도 증적은 1행이다. 생략하면 이벤트마다 새 키가 되어 매번 새 증적이 된다.- 표준필드는 조항별 화이트리스트만 받는다. 오타·미정의 키는 경고 후 드롭된다(이벤트는 거부되지 않는다).
- 값에 원문(prompt/response)이나 판정어("준수/위반")를 넣지 마십시오.
값 규칙·형식 제약 — 타입이 틀리면 조용히 버려진다 (★ 중요)
각 표준필드는 정해진 타입이 있습니다. 타입이 맞지 않는 값은 서버가 해당 필드만 버립니다(drop). 이벤트는 202(접수)로 응답되고 drop 사유는 서버 로그에만 남아 응답에는 나타나지 않으므로, 접수에 성공해도 감사 시점에 해당 필드가 비어 있을 수 있습니다. 전송 전 dry_run=True·validate() 로 확인하시기 바랍니다.
| 타입 | 허용 값 | 틀리면 | 예시 |
|---|---|---|---|
| 문자(text) | 아무 문자열(500자 초과 잘림) | 숫자·객체도 문자열화 | supervisor_id="sup-1" |
| 숫자(numeric) | 숫자·숫자문자열 | 문자 → 드롭 | confidence=0.68 |
| 정수(bigint) | 정수·정수문자열 | 소수 내림·비숫자 → 드롭 | evidence_document_id=123 |
| 불리언(boolean) | true/false·"true"/"false" |
1·"yes" → 드롭 |
gov_confirmation_requested=true |
| 날짜(date) | 파싱 가능 날짜(YYYY-MM-DD 저장) |
형식 불명 → 드롭 | agent_designation_date="2026-02-10" |
| 시각(timestamptz) | ISO8601 | 파싱 불가 → 드롭 | review_timestamp="2026-08-22T10:00:00Z" |
null·빈 문자열 = 드롭. 값이 없으면 넣지 마십시오.- 산업별(로봇·음성·자율주행·의료·채용·금융) 계측 예시·전송 흐름은 설치 및 사용 설명서(PDF) 참조.
조항별 표준필드 사전 (컬럼 · 타입 · 의미)
서버가 받는 조항별 표준필드 전체다. 여기 없는 이름은 드롭된다. evidence_block(조항, **필드) / s.evidence(조항, ...) 에 아래 컬럼을 넣는다.
제31조 투명성 — ev_transparency
| 컬럼 | 타입 | 의미 |
|---|---|---|
| disclosure_status | text | AI 이용 고지 수행 상태 |
| disclosure_method | text | 고지 방법(화면·음성·문서) |
| disclosure_content_hash | text | 고지 문구 해시 |
| disclosure_version | text | 고지 문구 버전 |
| ai_generated_label | text | 생성물 AI 표시 여부 |
| label_method | text | 표시 방법(워터마크·문구) |
| output_id | text | 표시 대상 산출물 식별자 |
| synthetic_media | text | 합성 미디어 여부 |
| media_type | text | 미디어 종류 |
| label_visibility | text | 표시 노출 수준 |
제32조 안전성 — ev_safety
| 컬럼 | 타입 | 의미 |
|---|---|---|
| model_id · model_version | text | 모델 식별자 · 버전 |
| training_compute | numeric | 학습 연산량(FLOPs) |
| system_risk_level | text | 시스템 위험 등급 |
| risk_id · risk_type · risk_level | text | 위험 식별자 · 유형 · 수준 |
| risk_action · risk_status | text | 위험 조치 · 처리 상태 |
| incident_id · incident_type | text | 사고 식별자 · 유형 |
| incident_severity | text | 사고 심각도 |
| incident_action · incident_status | text | 사고 대응 · 상태 |
제33·34조 고영향 — ev_high_impact
| 컬럼 | 타입 | 의미 |
|---|---|---|
| high_impact_review_status/_basis/_result | text | 고영향 판단 상태·근거·결과(제33) |
| use_case · ai_domain | text | 활용 사례 · AI 적용 분야 |
| input_reference · output_reference | text | 입력·출력 근거 참조 |
| gov_confirmation_requested | boolean | 정부 확인 요청 여부 |
| supervisor_id/_name/_role/_contact | text | 감독자 식별자·이름·직책·연락처(제34) |
| reviewer_id · reviewer_role | text | 검토자 식별자 · 직책 |
| review_timestamp | timestamptz | 사람 검토 시각 |
| review_result | text | 사람 검토 결과 |
| human_override · override_reason | text | 사람 오버라이드 여부 · 사유 |
| decision_basis · explanation | text | 결정 근거·설명 / 판단 설명 |
| training_data_reference | text | 학습데이터 참조 |
| user_protection_action · protection_reason | text | 이용자 보호 조치 · 사유 |
| risk_mgmt_ref | text | 위험관리 문서 참조 |
| evidence_document_id | bigint | 근거 문서 FK(정수) |
| evidence_document_hash | text | 근거 문서 해시 |
제35조 영향평가 — ev_assessment
| 컬럼 | 타입 | 의미 |
|---|---|---|
| usage_pattern | text | 이용 유형 |
| affected_user_group · affected_action | text | 영향받는 이용자 집단 · 행위 |
| affected_group_definition | text | 집단 정의 |
| fundamental_right_type | text | 관련 기본권 유형 |
| social_economic_impact | text | 사회·경제 영향 |
| evaluation_metric/_method/_result | text | 평가 지표·방법·결과 |
| risk_mitigation_plan · improvement_plan | text | 위험 완화 · 개선 계획 |
| assessment_document_hash | text | 영향평가 문서 해시 |
| public_procurement_target | boolean | 공공조달 대상 여부 |
제36조 국내대리인 — companies 확장(회사 마스터 1행)
| 컬럼 | 타입 | 의미 |
|---|---|---|
| company_country | text | 사업자 국가 |
| has_korean_address | boolean | 국내 주소 보유 여부 |
| annual_revenue · ai_service_revenue | bigint | 연매출 · AI서비스 매출 |
| domestic_user_daily_avg | bigint | 국내 일평균 이용자 수 |
| domestic_agent_name · _contact | text | 국내대리인 이름 · 연락처 |
| agent_designation_date | date | 대리인 지정일(YYYY-MM-DD) |
| agent_report_date | date | 대리인 신고일(YYYY-MM-DD) |
확인 3종 — 전송 전 로컬 확인
xc.init(..., dry_run=True) # ① 전송 0. payload·조항매핑·필수 표준필드·마스킹 결과를 콘솔에 출력
report = xc.validate(evt) # ② 조항별 필수 표준필드 «사실» 집계 (판정 아님)
print(xc.preview()) # ③ 1콜 output 로컬 확인
python -m xsoft_comply.preview # 설치 확인용 1콜 출력
validate() 는 「필수 N개 중 M개가 비어 있다」 는 사실만 돌려준다.
준수·위반 여부를 판정하지 않는다(변호사법 109조).
LangChain 통합
from langchain_openai import ChatOpenAI
handler = xc.langchain_handler()
llm = ChatOpenAI(callbacks=[handler])
with xc.session(agent_id="lc-agent") as s:
response = llm.invoke("안녕하세요")
langchain 또는 langchain-core 가 설치돼 있으면 BaseCallbackHandler 를 상속한다.
없으면 순수 Python 클래스로 동작한다.
OpenAI Agents SDK 통합
import asyncio
import xsoft_comply as xc
from agents import Agent, Runner, function_tool
@function_tool
def lookup_order(order_id: str) -> str:
return f"주문 {order_id} 상태 = 배송중"
agent = Agent(name="order-bot", model="gpt-4o-mini", tools=[lookup_order])
xc.instrument(agent) # AgentHooks 주입
async def main():
with xc.session(agent_id="order-bot"):
await Runner.run(
agent, "주문번호 A-1024 상태 알려줘.",
hooks=xc.agents_run_hooks(), # RunHooks 주입(핸드오프까지 잡는다)
)
asyncio.run(main())
기록되는 것 = 에이전트 시작·종료, 도구 호출(이름·소요시간), LLM 호출(모델·토큰 수), 핸드오프. 원문(입력·출력)은 보내지 않고 길이만 남긴다. 두 훅을 함께 써도 이중기록되지 않는다(실행훅이 에이전트훅이 이미 붙은 에이전트는 건너뛴다).
이벤트 종류별 로그 토글
xc.init(..., log_events={"tool_call": False, "data_access": False})
끈 종류는 seq 를 소비하지 않는다 → 체인이 끊기지 않는다.
session_start · session_end 는 끌 수 없다(끄면 trace 자체가 성립하지 않는다).
정책 자동차단
rules = [
{"id": "no-mass-delete", "when": {"tool": "crm.delete_all"},
"effect": "block", "reason": "대량 삭제는 사람 승인 후에만"},
{"id": "export-warn", "when_re": {"tool": r"^crm\.export"}, "effect": "warn"},
]
xc.init(..., policy=rules) # policy_enforce=False 면 기록만 하고 막지 않는다
with xc.session(agent_id="support-bot") as s:
outcome = s.run_tool("crm.delete_all", delete_everything)
if outcome.blocked:
... # 도구는 **호출되지 않았다**
차단은 예외를 던지지 않는다. outcome.blocked 로 알려주고, 감사기록에는
policy_result="blocked" · metadata.policy_rule 이 남는다.
s.check("tool_call", tool="…") 로 평가만 할 수도 있다.
전자서명 (ed25519 · 의존성 0)
xc.init(..., sign=True) # 키가 없으면 로컬에 만들어 0600 으로 보관
print(xc.signing_public_key()) # 감사인에게 넘길 공개키(개인키는 반환 경로 없음)
서명 대상 = trace_id·seq·event_id·type·occurred_at·라벨 컬럼·metadata 해시·직전 서명. DB 에 저장된 행만 보고 검증할 수 있고, 값이 한 글자만 달라져도 깨진다. 서명은 전송 스레드에서 한다(호출 스레드는 O(1) 유지).
원문 보관 (객체스토리지 · 옵션)
xc.init(
...,
log_llm_content=True,
r2={"endpoint": "https://<account>.r2.cloudflarestorage.com",
"bucket": "comply-raw", "access_key_id": "...", "secret_access_key": "..."},
)
s.llm_call(model="gpt-4o-mini", content=prompt_and_response)
원문은 고객 소유 버킷으로 직접 올라간다(우리 수집 API 를 거치지 않는다).
이벤트에는 위치(content_uri)와 길이·해시만 남는다. 버킷 장애 시에도 이벤트는 정상 적재되고
원문은 로컬 스풀에도 남기지 않는다.
보관기간(retention)
xc.init(..., retention_days=3650) # 기본 1825일(5년) · 초장기 애드온은 늘려 잡는다
이벤트 metadata 에 _retain_until(만료일)이 남는다. 만료분 정리는 운영측 도구가 한다
(sdk/tools/retention.py).
수동 flush / 종료
xc.flush(timeout=5.0) # 버퍼가 빌 때까지 최대 5초 대기
xc.shutdown() # 전송 스레드 정지
프로세스 종료 시 atexit 핸들러가 자동으로 최대 2초 flush 를 시도한다.
장애격리 설계 — 고객 가용성 보호
xsoft comply SDK 는 설계상 고객 애플리케이션의 가용성을 침해하지 않습니다. 다음 8개 계약이 이를 보장한다.
1. 모든 공개 함수는 예외를 밖으로 내보내지 않는다
init() · session() · s.llm_call() 등 모든 공개 함수는 최상위 try/except BaseException 으로 감싼다.
KeyboardInterrupt · SystemExit 만 재raise 한다(프로세스 종료 신호를 막지 않기 위해).
# SDK 내부 구조 (단순화)
def llm_call(self, ...):
try:
evt = build_event(...)
buffer.put(evt) # O(1) 비블로킹
except (KeyboardInterrupt, SystemExit):
raise
except BaseException:
pass # 모든 오류 삼킨다
2. 호출 스레드는 네트워크를 만지지 않는다
이벤트 기록은 queue.put_nowait() (O(1)) 만 실행한다.
큐가 가득 차면 가장 오래된 항목을 먼저 버리고 새 항목을 넣는다. 블로킹 없음.
고객 스레드 → put_nowait(event) → [queue] → daemon 스레드 → HTTP POST
3. 전송은 daemon 백그라운드 스레드 1개
threading.Thread(daemon=True) 로 생성된다.
daemon 스레드는 Python 인터프리터가 종료될 때 강제 종료되므로 고객 프로세스 종료를 막지 않는다.
4. HTTP 타임아웃: connect 2s / read 3s
urllib.request 에 합산 5s 타임아웃을 설정한다.
requests · httpx 등 외부 라이브러리 의존성 없음.
5. 전송 실패 시 디스크 스풀 → 지수 백오프
연결 거부 · 5xx · 타임아웃 등 모든 네트워크 장애 시:
- 이벤트를
~/.xsoft_comply/spool/events.jsonl에 JSONL 로 append - 백오프 대기 (1s → 2s → 4s → 8s → 30s 상한)
- 백오프 후 재전송 시도
6. 스풀 파일 상한 (기본 32 MB)
스풀 디렉터리 총 크기가 32 MB 를 초과하면 오래된 파일부터 삭제한다. 디스크 쓰기 실패도 삼킨다.
7. init() 전에 이벤트를 기록해도 죽지 않는다
큐가 None 인 상태에서 buffer.put() 은 즉시 반환(no-op) 된다.
8. atexit에 flush 등록 (타임아웃 2s)
import atexit
atexit.register(_atexit_handler) # init() 최초 호출 시 1회 등록
def _atexit_handler():
flush(timeout=2.0) # 2초 후 반드시 반환
프로세스가 정상 종료될 때 버퍼에 남은 이벤트를 최대 2초 안에 전송 시도한다. 2초 안에 완료되지 않아도 프로세스 종료를 막지 않는다.
원문 미저장 보장
log_llm_content=True 로 설정해도 원문(prompt/response) 은 설계상 전송되지 않습니다.
대신 content_len (길이) 와 content_sha256_8 (sha256 앞 8자) 만 metadata 에 포함됩니다.
다음 metadata 키가 있으면 SDK 에서 자동 제거하고 _dropped_keys 에 이름만 기록합니다:
prompt, response, messages, content, input, output 등 21개.
PII 마스킹
전송 전 로컬에서 자동 마스킹된다:
- 한국 주민등록번호
\d{6}-?\d{7}→******-******* - 휴대전화
01X-XXXX-XXXX→010-****-**** - 이메일 →
a***@domain - 카드번호 16자리 →
앞4-****-****-뒤4
마스킹 발생 시 metadata.pii_masked = true.
라이선스
MIT
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 xsoft_comply-0.1.1.tar.gz.
File metadata
- Download URL: xsoft_comply-0.1.1.tar.gz
- Upload date:
- Size: 77.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
16e60140c179d66295cf1df63c8bc3be1d7aa3f1e03c81546d8d80bc47bfd451
|
|
| MD5 |
e703b47e47f9b5bf18e83f9267fbbf3d
|
|
| BLAKE2b-256 |
c9905bbac5aab8cbabf7defd2a8d33bb58a5bbd3c07fc581aabb26e71ec39819
|
File details
Details for the file xsoft_comply-0.1.1-py3-none-any.whl.
File metadata
- Download URL: xsoft_comply-0.1.1-py3-none-any.whl
- Upload date:
- Size: 80.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fc98d8cd169a8df550ae4ea9bda05d9a20bdabc5c21e63a6e3d514db65279321
|
|
| MD5 |
c5c3faa3ad031825c389a1b805a981b2
|
|
| BLAKE2b-256 |
f343936b1477fadf1fd32ab88aede512ff2c2de344bde3daff7af390a5e9cd59
|