본문 바로가기
AI

AI 개인 변호사 만드는 방법 - AI 변호사 (법제처 API)

by marketinkerbell 2026. 9. 8.

 

법제처가 공짜로 열어준 API 사용해서 내 개인 변호사 만드는 방법! 

내가 직면한 상황에 해당 되는 법만 감시하는 도구를 만들어 봤습니다.

시작은 아찔한 실수였습니다

집을 사려고 지역별 시세를 정리하다가, 어떤 동네가 규제지역인지 확인할 일이 생겼습니다. 검색해서 나온 블로그 글을 보고 "여기는 규제 안 걸렸네" 하고 계획을 짰죠.

며칠 뒤 원문을 다시 보니 규제지역이 12곳이었습니다. 제가 본 글에는 3곳만 적혀 있었고요. 제가 안전하다고 믿었던 동네가 그 12곳 안에 있었습니다. 규제지역이면 전세 끼고 사는 게 아예 안 되니까, 전략 자체가 성립하지 않는 상황이었습니다.

다행히 실행 전이라 계획만 고치면 됐지만, 여기서 배운 게 있습니다.

블로그 요약본은 언제 쓰였는지 모릅니다. 법은 계속 바뀌는데 글은 그대로 남아 있죠. 돈이 걸린 판단은 원문을 봐야 합니다.

챗봇 말고 감시기를 만드세요

"AI 변호사"라고 하면 보통 질문하면 답해주는 챗봇을 떠올립니다. 그런데 곰곰이 생각해 보면, 조문 찾아 읽는 건 이미 AI한테 물어보면 웬만큼 됩니다. 진짜 문제는 다른 데 있었어요.

상황 챗봇 감시기
"양도세 비과세 요건이 뭐야?" 잘 답함 해당 없음
어제 그 요건이 바뀌었다 물어보지 않으면 모름 아침에 알려줌

몰라서 사고 나는 게 아닙니다. 안다고 믿었던 게 바뀌어서 사고가 납니다. 그래서 저는 챗봇 대신 감시기를 만들었습니다. 내 계획에 직접 걸린 법만 골라서, 개정되면 알려주는 물건이요.

법제처가 API를 공짜로 엽니다

의외로 잘 안 알려져 있는데, 법제처 국가법령정보센터가 191개 OPEN API를 무료로 제공합니다.

  • 현행 법령 전문, 연혁, 영문 법령
  • 대법원 판례
  • 헌법재판소 결정례
  • 법령해석례 (정부 부처 유권해석)
  • 조세심판원 결정례
  • 지자체 조례까지

open.law.go.kr 에서 신청하면 1~2일 안에 승인됩니다. 신청하면 OC 값(가입 이메일의 앞부분)을 받는데, 이걸 파라미터로 넣으면 끝입니다.

신청 전에 5초 만에 확인해 보기

테스트용 키로 바로 찔러볼 수 있습니다. 터미널에 이거 한 줄 넣어보세요.

curl "https://www.law.go.kr/DRF/lawSearch.do?OC=test&target=law&type=XML&query=소득세법&display=3"

XML이 쏟아지면 성공입니다. 법령명, 시행일자, 공포일자, 소관부처가 다 들어 있습니다.

엔드포인트는 딱 두 개입니다

용도 주소
검색 /DRF/lawSearch.do
본문 /DRF/lawService.do

target 파라미터만 바꾸면 종류가 달라집니다.

law     법령
prec    판례
expc    법령해석례
detc    조세심판원 결정례
admrul  행정규칙

검색 코드

import re
import requests

SEARCH_URL = "https://www.law.go.kr/DRF/lawSearch.do"
OC = "test"  # 신청하면 받는 본인 ID


def _text(node, tag):
    m = re.search(rf"<{tag}>(?:<!\[CDATA\[)?(.*?)(?:\]\]>)?</{tag}>", node, re.S)
    return m.group(1).strip() if m else ""


def search_law(query, display=5):
    r = requests.get(SEARCH_URL, params={
        "OC": OC, "target": "law", "type": "XML",
        "query": query, "display": display,
    }, timeout=20)
    r.raise_for_status()

    out = []
    for m in re.finditer(r"<law id=\"\d+\">(.*?)</law>", r.text, re.S):
        b = m.group(1)
        out.append({
            "name": _text(b, "법령명한글"),
            "effective": _text(b, "시행일자"),      # 20260701
            "promulgated": _text(b, "공포일자"),
            "mst": _text(b, "법령일련번호"),        # 본문 조회용 키
            "status": _text(b, "현행연혁코드"),     # 현행 / 연혁
            "ministry": _text(b, "소관부처명"),
        })
    return out


for row in search_law("소득세법"):
    print(row["name"], row["effective"], row["mst"])

결과는 이렇게 나옵니다.

소득세법 20260701 280405
소득세법 시행령 20260701 286211
소득세법 시행규칙 20260701 286379

여기서 한 번 크게 헤맸습니다

본문을 가져오는 건 쉽습니다. 아까 받은 mst 값을 넣으면 됩니다.

r = requests.get("https://www.law.go.kr/DRF/lawService.do", params={
    "OC": OC, "target": "law", "type": "XML", "MST": "280405",
}, timeout=30)
print(len(r.text))   # 611660

소득세법 전문이 61만 자입니다. 통째로는 못 쓰니까 필요한 조문만 골라야 하는데, 저는 처음에 이렇게 했습니다.

# 이렇게 하면 안 됩니다
arts = re.findall(r"<조문내용>(.*?)</조문내용>", r.text, re.S)
found = [a for a in arts if "1세대 1주택" in a]
print(len(found))   # 0 ... ???

0건이 나옵니다. 소득세법에 1세대 1주택 얘기가 없을 리가 없죠. XML을 직접 뜯어보니 이유가 나왔습니다.

<조문단위>
  <조문번호>89</조문번호>
  <조문내용>제89조(비과세 양도소득)</조문내용>   ← 제목만 있음
  <항>
    <항내용>① 다음 각 호의 소득에 대해서는...</항내용>   ← 본문은 여기
    <호><호내용>1. 파산선고에 의한 처분으로...</호내용></호>
    <호><호내용>2. 농지의 교환 또는 분합으로...</호내용></호>
  </항>
</조문단위>
조문내용에는 제목만 들어 있습니다. 실제 내용은 항내용, 호내용, 목내용에 흩어져 있어요. 그래서 조문단위를 통째로 잡아서 안에 있는 걸 전부 이어붙여야 합니다.
CDATA = re.compile(r"<!\[CDATA\[(.*?)\]\]>", re.S)


def article_text(unit):
    """조문단위 하나를 사람이 읽는 텍스트로"""
    parts = []
    for m in re.finditer(r"<(조문내용|항내용|호내용|목내용)>(.*?)</\1>", unit, re.S):
        raw = m.group(2)
        c = CDATA.search(raw)
        text = (c.group(1) if c else raw).strip()
        if text:
            parts.append(text)
    return "\n".join(parts)


def get_article(mst, keyword=None):
    r = requests.get("https://www.law.go.kr/DRF/lawService.do", params={
        "OC": OC, "target": "law", "type": "XML", "MST": mst,
    }, timeout=30)
    units = re.findall(r"<조문단위[^>]*>(.*?)</조문단위>", r.text, re.S)
    arts = [t for t in (article_text(u) for u in units) if t]
    if keyword:
        arts = [a for a in arts if keyword in a]
    return arts


for a in get_article("280405", "1세대 1주택")[:1]:
    print(a[:200])

이제 제대로 나옵니다.

제95조(양도소득금액과 장기보유 특별공제액)
① 양도소득금액은 양도차익에서 장기보유 특별공제액을 공제한 금액으로 한다.
② 제1항에서 "장기보유 특별공제액"이란 ... 대통령령으로 정하는 1세대 1주택
   (이에 딸린 토지를 포함한다)에 해당하는 자산의 경우에는 ...

AI한테 물어볼 때 이 원문을 같이 던져주면 답의 질이 확 올라갑니다. 요약본을 요약한 걸 다시 요약하는 게 아니라, 진짜 법 조문을 읽고 답하는 거니까요.

본론: 감시기 만들기

핵심 아이디어는 단순합니다. 시행일자와 공포일자를 저장해 두고, 매일 조회해서 달라지면 알린다.

import json
from pathlib import Path

CACHE = Path("law_snapshot.json")

# 내 상황에 직접 걸린 법만 고릅니다. 많으면 소음이 됩니다.
WATCH = [
    {"name": "소득세법",
     "affects": "집 팔 때 양도세 비과세 요건, 급여 과세"},
    {"name": "지방세특례제한법",
     "affects": "생애최초 취득세 감면 일몰 여부"},
    {"name": "부동산 거래신고 등에 관한 법률",
     "affects": "토지거래허가구역 지정 여부"},
    {"name": "주택임대차보호법",
     "affects": "세입자 계약갱신청구권"},
]


def check():
    prev = {}
    if CACHE.exists():
        prev = {x["name"]: x for x in json.loads(CACHE.read_text())}

    rows, changes = [], []
    for item in WATCH:
        hits = [h for h in search_law(item["name"])
                if h["status"] == "현행" and h["name"] == item["name"]]
        if not hits:
            continue
        row = {**hits[0], "affects": item["affects"]}
        old = prev.get(item["name"])
        if old and old["effective"] != row["effective"]:
            changes.append({**row, "before": old["effective"]})
        rows.append(row)

    CACHE.write_text(json.dumps(rows, ensure_ascii=False, indent=1))
    return changes


for c in check():
    print(f"[개정] {c['name']}")
    print(f"  시행 {c['before']} -> {c['effective']}")
    print(f"  영향: {c['affects']}")

여기서 중요한 건 affects 필드입니다. "소득세법이 개정됐습니다"만 오면 그래서 뭐 어쩌라고 싶거든요. "이게 내 어떤 계획에 걸리는지"를 같이 적어두면 알림을 받았을 때 바로 판단이 섭니다.

매일 아침 돌리기

서버가 있으면 cron에 걸고, 없으면 노트북 켤 때 돌아가게 해도 됩니다. 저는 알림을 텔레그램 봇으로 받고 있습니다.

# crontab -e
30 7 * * * /path/to/python /path/to/law_check.py

텔레그램 봇은 @BotFather 에서 5분이면 만들고, 메시지 보내는 건 이게 전부입니다.

requests.post(
    f"https://api.telegram.org/bot{TOKEN}/sendMessage",
    json={"chat_id": CHAT_ID, "text": message},
)

판례도 같이 봅니다

법 조문만으로 답이 안 나오는 경우가 많습니다. 그럴 때 판례를 붙이면 훨씬 선명해집니다. target만 바꾸면 됩니다.

def search_prec(query, display=5):
    r = requests.get(SEARCH_URL, params={
        "OC": OC, "target": "prec", "type": "XML",
        "query": query, "display": display,
    }, timeout=20)
    out = []
    for m in re.finditer(r"<prec id=\"\d+\">(.*?)</prec>", r.text, re.S):
        b = m.group(1)
        out.append({
            "title": _text(b, "사건명"),
            "case_no": _text(b, "사건번호"),
            "date": _text(b, "선고일자"),
            "court": _text(b, "법원명"),
        })
    return out


for p in search_prec("양도소득세", 3):
    print(p["date"], p["case_no"])
    print("  ", p["title"][:60])

"양도소득세"로 검색하면 5,382건이 나옵니다. 최근 것부터 몇 개만 봐도 요즘 어떤 게 다투어지는지 감이 옵니다.

쓰면서 느낀 것

검색 능력이 아니라 갱신 능력이 핵심입니다. 모르는 걸 찾는 건 이미 잘 됩니다. 문제는 내가 안다고 믿는 게 낡았을 때인데, 그건 물어볼 생각조차 안 하니까 영영 모릅니다. 감시기는 그 구멍을 메웁니다.

감시 목록은 적을수록 좋습니다. 처음엔 스무 개쯤 넣고 싶어지는데, 그러면 알림이 소음이 되고 결국 안 보게 됩니다. 저는 7개로 시작했습니다. 내 돈이 실제로 걸린 것만요.

원문을 던져주면 AI 답이 달라집니다. 같은 질문이라도 "법 조문 원문 + 판례 3건"을 같이 주면 훨씬 정확해집니다. 애매하면 애매하다고 말해주고요.

당연한 얘기지만 이건 변호사가 아닙니다. 법 원문을 읽기 쉽게 가져오고 바뀌면 알려주는 도구일 뿐이에요. 실제로 돈이 크게 걸린 판단은 전문가한테 확인받아야 합니다. 저는 이걸 "전문가한테 물어볼 질문을 정확하게 만드는 도구"로 씁니다. 아무것도 모르고 가는 것보다, 조문 번호를 짚으면서 묻는 게 훨씬 나은 답을 받거든요.

정리

  1. open.law.go.kr 에서 API 신청 (무료, 1~2일)
  2. 엔드포인트는 lawSearch.do(검색), lawService.do(본문) 두 개
  3. 본문 파싱은 조문단위로 묶어서 (조문내용에는 제목만 있음)
  4. 내 상황에 걸린 법만 5~7개 골라 시행일 저장
  5. 매일 아침 대조해서 바뀌면 알림

주말 반나절이면 만듭니다. 저는 이걸로 세금 관리해주는 "AI 세무사"도 하나 더 만들었는데, 그건 아래 글에서 확인해주세요 :)

 

https://devfairy.tistory.com/entry/AI-%EA%B0%9C%EC%9D%B8-%EC%84%B8%EB%AC%B4%EC%82%AC-%EB%A7%8C%EB%93%9C%EB%8A%94-%EB%B0%A9%EB%B2%95-AI-%EC%84%B8%EB%AC%B4%EC%82%AC-%EB%B2%95%EC%A0%9C%EC%B2%98-API

 

 

댓글