#!/usr/bin/env python3
"""실습 3 (6-8): 에이전트의 도구로서의 온톨로지.
구조: 도구 4종(온톨로지 조회) + 계획-실행 루프 + 근거 붙은 답 합성.
계획기는 규칙 기반 축소 구현 — 실전에서는 LLM이 도구 설명을 읽고
스스로 호출을 결정한다(6-8 본문). 도구 층과 추적 로그는 실전과 동일하다."""
import rdflib, json, re
g = rdflib.Graph().parse("psymap.owl")
PRE = """PREFIX :
PREFIX rdfs:
PREFIX owl:
PREFIX skos:
PREFIX dct:
"""
LEX = {}
for p in ("prefLabel", "altLabel"):
for c, l in g.query(PRE + f'SELECT ?c ?l WHERE {{ ?c skos:{p} ?l . FILTER(lang(?l)="ko") }}'):
LEX[str(l).replace(" ", "")] = c
def label_of(iri):
r = list(g.query(PRE + f'SELECT ?l WHERE {{ <{iri}> skos:prefLabel ?l . FILTER(lang(?l)="ko") }}'))
return str(r[0][0]) if r else str(iri).split("/")[-1]
# ---------- 도구 층: 에이전트에게 공개되는 함수 4종 ----------
def tool_define(term):
"용어의 정의와 출처를 돌려준다"
iri = LEX.get(term.replace(" ", ""))
if not iri: return {"error": "용어 없음"}
d = list(g.query(PRE + f'SELECT ?d WHERE {{ <{iri}> skos:definition ?d }}'))
s = list(g.query(PRE + f'SELECT ?s WHERE {{ <{iri}> dct:source ?s }}'))
return {"정의": str(d[0][0]) if d else None,
"출처": str(s[0][0])[:40] + "…" if s else "기재 없음"}
def tool_tasks_for(term):
"구성개념을 측정하는 과제 목록"
iri = LEX.get(term.replace(" ", ""))
if not iri: return {"error": "용어 없음"}
rows = g.query(PRE + f"""SELECT ?t WHERE {{ ?t rdfs:subClassOf ?r .
?r owl:onProperty :measures ; owl:someValuesFrom <{iri}> . FILTER(isIRI(?t)) }}""")
return {"과제": sorted(label_of(r[0]) for r in rows)}
def tool_components(term):
"구성 요소 목록"
iri = LEX.get(term.replace(" ", ""))
if not iri: return {"error": "용어 없음"}
rows = g.query(PRE + f"""SELECT ?c WHERE {{ ?c rdfs:subClassOf ?r .
?r owl:onProperty :component_of ; owl:someValuesFrom <{iri}> . FILTER(isIRI(?c)) }}""")
return {"구성 요소": sorted(label_of(r[0]) for r in rows)}
def tool_ca_link(term):
"Cognitive Atlas 대응 레코드(공용 좌표)"
iri = LEX.get(term.replace(" ", ""))
if not iri: return {"error": "용어 없음"}
rows = list(g.query(PRE + f'SELECT ?m WHERE {{ <{iri}> skos:exactMatch ?m }}'))
return {"CA": str(rows[0][0]).rsplit("/id/", 1)[-1].rstrip("/") if rows else "매핑 없음(미확인 기록)"}
TOOLS = {"define": tool_define, "tasks_for": tool_tasks_for,
"components": tool_components, "ca_link": tool_ca_link}
# ---------- 계획-실행 루프 (계획기는 규칙 기반 축소 구현) ----------
def plan(question):
term = next((t for t in sorted(LEX, key=len, reverse=True)
if t in question.replace(" ", "")), None)
steps = []
if term:
steps.append(("define", term))
if re.search("측정|과제|어떻게 재", question): steps.append(("tasks_for", term))
if re.search("구성|요소", question): steps.append(("components", term))
if re.search("측정|과제", question): steps.append(("ca_link", term))
return term, steps
def run(question):
print(f"질문: {question}")
term, steps = plan(question)
evidence = {}
for i, (tool, arg) in enumerate(steps, 1):
out = TOOLS[tool](arg)
evidence[tool] = out
print(f" [{i}] 호출 {tool}({arg}) → {json.dumps(out, ensure_ascii=False)}")
if not evidence:
print(" 답: 온톨로지에 없는 용어 — 도구 없이는 답하지 않는다\n"); return
parts = []
if "define" in evidence: parts.append(f"{term}은(는) {evidence['define']['정의']}")
if "tasks_for" in evidence: parts.append(f"대표 측정 과제는 {', '.join(evidence['tasks_for']['과제'])}이다")
if "ca_link" in evidence: parts.append(f"공용 좌표는 Cognitive Atlas {evidence['ca_link']['CA']}")
print(" 답(근거 부착):", ". ".join(parts), f"[출처: {evidence['define']['출처']}]\n")
run("작업기억이 무엇이고, 어떻게 측정하는가?")
run("기억 응고화란?")