Procedural Graph 이해와 구현: 소재 연구 에이전트로 확장하기
Procedural Graph 이해와 구현: 소재 연구 에이전트로 확장하기
작성일: 2026-09-11
대상: 소재 연구자, 연구 데이터 플랫폼 개발자, AI 에이전트 입문자
기준 논문: Procedural Graphs: Self-Evolving Execution Structures for LLM Agents, arXiv:2609.09153v1
이 문서는 논문의 핵심을 짧게 정리한 뒤, 독자적으로 작성한 교육용 코드와 소재 분야 설계 아이디어를 제공합니다. 코드는 공식 구현이나 벤치마크 재현이 아닙니다. 소재 분야 제안은 논문에서 검증된 결과가 아닙니다.
목차
- 논문의 핵심 개념
- 소재 연구 예제로 직관 잡기
- 비슷한 개념과 역할 구분
- 파이썬 구현: 단위 오류를 고치는 절차 그래프
- 실행 결과와 코드 해설
- 실제 LLM 연결 설계
- 소재 분야 응용 아이디어
- D3Square·Materials Square 적용 설계
- 평가와 실험 계획
- 한계, 확장 과제, 읽기 순서
1. 논문의 핵심 개념
Procedural Graph(PG)는 에이전트의 절차 지식을 외부 그래프로 저장하고, 현재 상황에 맞는 다음 행동을 조언하는 구조다. 노드는 도구·추론·상태를, 연결은 절차 간 전이를 표현한다. 연결에는 condition, guidance, pitfalls가 붙는다.
실행 중에는 마지막 절차를 노드에 매칭하고 주변 그래프를 가져온다. 안내용 LLM이 최근 이력과 함께 읽어 조언을 생성하며, solver LLM이 이를 받아 행동한다. 논문의 기본 설정은 2-hop 이웃과 최근 3단계 이력이며, 매칭에 실패하면 전체 그래프를 사용한다. 조언은 프롬프트에 추가되므로 경로를 강제하는 실행기는 아니다.
개선은 작업 묶음 이후 별도로 수행한다. Refiner LLM이 성공·실패 기록으로 그래프 수정을 제안하고, 별도 검증 점수가 유지되거나 상승하면 채택한다. 거절된 수정도 기록한다. 실행 중 그래프는 고정되고, 모델 가중치를 재학습하지 않는다. 논문 §3
| 기호 | 읽는 방법 |
|---|---|
| G=(V,R,E,\Phi) | 노드, 관계 종류, 연결, 연결 속성으로 구성된 그래프 |
| (u,r,v)\in E | 절차 u에서 관계 r로 절차 v에 연결 |
| g_t | 현재 단계의 상황별 조언 |
| a_t | solver가 선택한 행동 |
논문은 여러 에이전트 벤치마크에서 개선을 보고하지만, 소재 연구 성능을 입증한 연구는 아니다. 논문 §4–5
이하 내용은 교육·응용을 위해 독자적으로 설계한 예시다.
2. 소재 연구 예제로 직관 잡기
마모 실험 데이터가 들어왔다고 하자. 한 실험자는 거리 2 m를, 다른 실험자는 2000 mm를 기록했다. 숫자만 모델에 넣으면 동일한 실험 조건이 전혀 다르게 해석될 수 있다.
숙련 연구자라면 “모델부터 바꾸기 전에 단위를 확인하자”고 말할 수 있다. 이 판단을 다음처럼 저장해 두면 에이전트가 필요할 때 참고할 수 있다.
| 출발 절차 | 관계 | 다음 절차 | 조건 | 조언 | 피할 실수 |
|---|---|---|---|---|---|
| 입력 점검 | IF | 단위 변환 | 거리 단위가 mm | m로 변환 | 숫자만 전달 |
| 입력 점검 | IF | 계산 | 거리 단위가 m | 계산 도구 호출 | 이중 변환 |
| 단위 변환 | NEXT | 계산 | 변환 완료 | 변환값 사용 | 원본값 재사용 |
| 계산 | NEXT | 보고 | 결과 존재 | 단위와 가정 표시 | 실험값으로 오인 |
flowchart TD
A["입력 점검"] -->|mm| B["단위 변환"]
A -->|m| C["계산"]
B --> C
C --> D["결과 보고"]
이 그림은 가능한 절차를 나타낸다. 실제 연구에서는 단위 정보가 없을 때 연구자에게 문의하거나, 실험 기록에서 확인하는 별도 경로도 필요하다.
그래프의 노드는 매번 생성되는 개별 실험 기록과 구분해야 한다. 단위 변환은 재사용하는 절차이고, “실험 123의 거리 2000 mm를 2 m로 변환했다”는 특정 실행 기록이다. 둘을 분리해야 하나의 절차를 여러 연구에 적용하고, 결과의 출처도 추적할 수 있다.
3. 비슷한 개념과 역할 구분
다음 표는 시스템을 설계할 때 사용할 수 있는 실무적 구분이다. 서로 배타적인 분류는 아니다.
| 구성 | 주로 저장하는 것 | 연구 예시 |
|---|---|---|
| 온톨로지 | 용어와 관계의 의미·제약 | 열처리, 시편, 온도, 측정의 정의 |
| 지식그래프 | 구체적인 사실과 연결 | 시편 A가 실험 B에서 처리됨 |
| 문서형 가이드 | 자연어 업무 지침 | 마모 데이터 분석 매뉴얼 |
| Procedural Graph | 조건이 붙은 절차 관계 | 단위 불일치 시 변환 후 분석 |
| 실행 워크플로 | 실제 실행 순서와 상태 | 작업 제출·대기·재시도 |
| 실행 이력 | 어떤 일이 실제 일어났는지 | 입력 버전, 도구 호출, 결과 |
PG를 도입한다고 온톨로지나 작업 스케줄러를 대체할 필요는 없다. 개념 정의, 사실 조회, 행동 조언, 실제 실행을 서로 연결하는 편이 역할을 설명하기 쉽다.
“PG가 있으면 물리 제약을 반드시 지키는가?”라는 질문에는 별도 설계가 필요하다. 예를 들어 조성 합이 1이어야 한다는 조건은 실행 직전 코드로 검사하고, PG에는 위반 시 조성을 재정규화할지 입력을 다시 받을지 판단하는 절차를 담을 수 있다.
4. 파이썬 구현: 단위 오류를 고치는 절차 그래프
4.1 구현 범위
아래 코드는 Python 3.10 이상, 표준 라이브러리만으로 실행하도록 작성했다. API 키, 외부 모델, 그래프 데이터베이스가 필요 없다. 다음 코드 블록 전체를 pg_demo.py로 저장하고 python3 pg_demo.py를 실행한다.
| 구성 | 교육용 구현 | 실제 시스템에서 교체할 부분 |
|---|---|---|
| 그래프 | dataclass와 리스트 | 규모에 따라 JSON/DB |
| 현재 위치 | 직전 action 이름 | 도구·스킬 ID 매핑 |
| 조언 생성 | 조건 검사와 문자열 조합 | 상황을 해석하는 LLM |
| 행동 선택 | 첫 권고를 택하는 대역 | solver LLM |
| 그래프 수정 | 단위 변환을 추가하는 수작업 규칙 | 로그를 분석하는 refiner LLM |
| 평가 | 합성 문제의 정답 일치 | 도메인 평가 함수 |
| 거절 기록 | 메모리 리스트 | 다음 제안에도 제공하는 영속 기록 |
이 예제의 대역은 권고를 그대로 따른다. 따라서 LLM의 유연한 추론이나 자율적인 그래프 발견을 구현한 것은 아니다. 거절 기록은 저장까지만 구현하고 다음 refiner 입력에는 연결하지 않았다. 작은 사례에서 위치 탐색, 분기, 수정안 채택·거절이 어떻게 연결되는지 확인하는 목적이다.
4.2 가상의 계산 문제
실제 예측 모델 대신 다음 교육용 계산을 사용한다.
여기서 F는 하중(N), s는 거리(m), V는 마모 체적(mm³)이다. 계수는 설명을 위해 정한 값이며 실제 재료에 적용할 수 없다. 속도 효과나 미세조직 효과도 모델링하지 않는다. 마모 체적과 특정 마모율은 구분해야 하며, 이 식에서 V/(Fs)=c다.
10 N에서 2 m를 이동하면 0.04 mm³다. 2000 mm를 2000 m로 잘못 읽으면 40 mm³가 된다. 초기 그래프는 이 오류를 허용하고, 수정 그래프는 단위 변환 경로를 추가한다.
4.3 전체 실행 코드
"""교육용 PG: 외부 패키지/API 없이 실행. 실제 LLM/물성 예측은 사용하지 않음."""
from dataclasses import dataclass, asdict
from copy import deepcopy
import json
@dataclass(frozen=True)
class Edge:
source: str
relation: str
target: str
condition: str
guidance: str
pitfalls: str
@dataclass
class Graph:
nodes: set[str]
edges: list[Edge]
def validate(self):
if not {"Start", "report"} <= self.nodes:
raise ValueError("Start와 report가 필요합니다.")
seen = set()
for e in self.edges:
if e.source not in self.nodes or e.target not in self.nodes:
raise ValueError("존재하지 않는 노드를 참조합니다.")
key = (e.source, e.relation, e.target)
if key in seen:
raise ValueError("중복 삼중항입니다.")
seen.add(key)
# 교육용 구조 검사. 모든 조건에서 종료 가능함을 증명하지는 않습니다.
reachable = {"Start"}
for _ in self.nodes:
reachable |= {e.target for e in self.edges if e.source in reachable}
if "report" not in reachable:
raise ValueError("종료 노드에 도달할 수 없습니다.")
def neighborhood(self, active, hops=2):
if active not in self.nodes:
return list(self.edges) # 위치를 찾지 못하면 전체 그래프
frontier, visited, result = {active}, {active}, []
for _ in range(hops):
next_frontier = set()
for e in self.edges:
if e.source in frontier:
if e not in result:
result.append(e)
if e.target not in visited:
next_frontier.add(e.target)
visited |= next_frontier
frontier = next_frontier
return result
def initial_graph():
# 일부러 단위 정규화가 빠진 초안으로 시작합니다.
return Graph(
{"Start", "inspect", "predict", "report"},
[
Edge("Start", "NEXT", "inspect", "always",
"입력 단위와 값의 범위를 확인한다.", "열 이름만 보고 단위를 추측하지 않는다."),
Edge("inspect", "NEXT", "predict", "always",
"준비된 입력으로 계산한다.", "계산식의 적용 범위를 확인한다."),
Edge("predict", "NEXT", "report", "always",
"단위와 계산 가정을 함께 보고한다.", "교육용 수치를 실험값처럼 제시하지 않는다."),
],
)
def condition_holds(name, state):
# 이 문자열 조건 해석기는 본 문서의 추가 구현입니다.
# 임의 문자열을 eval()하지 않습니다.
if name == "always":
return True
if name == "needs_conversion":
return state["distance_unit"] != "m"
if name == "canonical_units":
return state["distance_unit"] == "m"
raise ValueError(f"알 수 없는 조건: {name}")
def make_guidance(graph, active, state, trace):
local = graph.neighborhood(active, hops=2)
eligible = [e for e in local
if e.source == active and condition_holds(e.condition, state)]
return {
"active": active,
"recent_trace": trace[-3:],
"local_graph": [asdict(e) for e in local],
"suggestions": [
{"action": e.target, "advice": e.guidance, "avoid": e.pitfalls}
for e in eligible
],
}
def toy_solver(guidance):
# 실제 solver LLM을 대신하는 결정적 대역입니다.
# 대역은 첫 권고를 따르지만, 실제 LLM 인터페이스는 이를 강제하지 않습니다.
choices = guidance["suggestions"]
return choices[0]["action"] if choices else "report"
def execute(action, state):
if action == "inspect":
return {"observed_unit": state["distance_unit"]}
if action == "normalize":
factors = {"m": 1.0, "mm": 0.001}
unit = state["distance_unit"]
if unit not in factors:
raise ValueError(f"지원하지 않는 거리 단위: {unit}")
state["distance"] *= factors[unit]
state["distance_unit"] = "m"
return {"distance_m": state["distance"]}
if action == "predict":
# 결함을 드러내기 위해 단위 검사를 생략한 레거시 도구를 모사합니다.
# 가상의 계수 c=0.002 mm^3/(N*m), V=c*F*s. 실제 재료 모델이 아닙니다.
state["volume_mm3"] = 0.002 * state["load_N"] * state["distance"]
return {"volume_mm3": state["volume_mm3"]}
if action == "report":
return {"volume_mm3": state.get("volume_mm3"), "finished": True}
raise ValueError(f"허용되지 않은 도구: {action}")
def rollout(graph, task):
graph.validate()
state = {k: task[k] for k in ("load_N", "distance", "distance_unit")}
active, trace = "Start", []
for _ in range(8):
guidance = make_guidance(graph, active, state, trace)
action = toy_solver(guidance)
observation = execute(action, state)
trace.append({"action": action, "observation": observation})
active = action
if action == "report":
break
# 정답은 solver에 전달하지 않고 평가 측에서만 참조합니다.
predicted = state.get("volume_mm3")
score = float(predicted is not None and
abs(predicted - task["expected"]) < 1e-9)
return {"task": task["id"], "score": score, "trace": trace}
def evaluate(graph, tasks):
runs = [rollout(graph, t) for t in tasks]
return sum(r["score"] for r in runs) / len(runs), runs
def toy_refiner(graph, training_runs):
# 성공/실패를 읽는 LLM refiner의 대역. 제안 로직은 직접 작성했습니다.
# 검증 데이터의 정답이나 실행 기록은 읽지 않습니다.
if not any(r["score"] == 0 for r in training_runs):
return deepcopy(graph)
candidate = deepcopy(graph)
candidate.nodes.add("normalize")
candidate.edges = [e for e in candidate.edges
if not (e.source == "inspect" and e.target == "predict")]
candidate.edges.extend([
Edge("inspect", "IF", "normalize", "needs_conversion",
"거리를 m로 변환한 뒤 계산한다.", "mm를 m로 전달하지 않는다."),
Edge("inspect", "IF", "predict", "canonical_units",
"거리가 m이면 계산으로 진행한다.", "변환한 값을 이중 변환하지 않는다."),
Edge("normalize", "NEXT", "predict", "always",
"정규화한 거리로 계산한다.", "변환 전 수치를 재사용하지 않는다."),
])
return candidate
def gate(current, candidate, validation, rejection_memory):
try:
candidate.validate()
except ValueError as exc:
rejection_memory.append({"reason": str(exc), "kind": "structural"})
return current, False
old_score, _ = evaluate(current, validation)
new_score, _ = evaluate(candidate, validation)
if new_score >= old_score:
return candidate, True
rejection_memory.append({
"reason": "validation score decreased",
"old": old_score, "new": new_score,
"candidate": [asdict(e) for e in candidate.edges],
})
return current, False
def main():
# 모두 합성 데이터이며 train/validation/test 사례를 분리합니다.
train = [
dict(id="tr_m", load_N=10, distance=2, distance_unit="m", expected=0.04),
dict(id="tr_mm", load_N=10, distance=2000, distance_unit="mm", expected=0.04),
]
validation = [
dict(id="va_m", load_N=20, distance=3, distance_unit="m", expected=0.12),
dict(id="va_mm", load_N=20, distance=3000, distance_unit="mm", expected=0.12),
]
test = [
dict(id="te_m", load_N=5, distance=4, distance_unit="m", expected=0.04),
dict(id="te_mm", load_N=5, distance=4000, distance_unit="mm", expected=0.04),
]
graph = initial_graph()
before, training_runs = evaluate(graph, train)
print(f"Initial training score: {before:.2f}")
candidate = toy_refiner(graph, training_runs)
rejected = []
graph, accepted = gate(graph, candidate, validation, rejected)
print("Repair accepted:", accepted)
print(f"Validation score: {evaluate(graph, validation)[0]:.2f}")
# 나쁜 수정안: normalize 경로를 없애 원래 오류를 재도입합니다.
retained, accepted_bad = gate(graph, initial_graph(), validation, rejected)
print("Regression accepted:", accepted_bad)
print("Rejected candidates:", len(rejected))
final_score, test_runs = evaluate(retained, test)
print(f"Final test score: {final_score:.2f}")
print("Test actions:", " -> ".join(x["action"] for x in test_runs[1]["trace"]))
print("Final graph:")
print(json.dumps([asdict(e) for e in retained.edges], ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()
5. 실행 결과와 코드 해설
앞부분의 출력은 다음과 같다. 이어서 최종 그래프가 JSON으로 출력된다.
Initial training score: 0.50
Repair accepted: True
Validation score: 1.00
Regression accepted: False
Rejected candidates: 1
Final test score: 1.00
Test actions: inspect -> normalize -> predict -> report
점수 1.00은 단 두 개의 합성 테스트를 통과했다는 뜻이다. 실제 연구 데이터에 대한 100% 정확도나 논문의 성능 재현을 의미하지 않는다.
5.1 데이터가 흐르는 과정
initial_graph()는 단위 변환을 빠뜨린 초안을 만든다.rollout()은 현재 노드 주변 그래프를 읽고 행동·관측 기록을 쌓는다.- m 입력은 맞지만 mm 입력은 틀리므로 초기 학습 점수는 0.50이다.
toy_refiner()가normalize노드를 넣고inspect의 경로를 분기한다.gate()는 다른 수치로 구성한 검증 사례에서 수정안을 평가한다.- 원래 결함을 다시 넣는 후보는 검증 점수가 하락하므로 거절한다.
- 최종 그래프를 별도 테스트 사례로 평가한다.
5.2 각 함수에서 볼 부분
| 함수 | 학습 포인트 |
|---|---|
Graph.neighborhood |
전체 그래프 대신 현재 위치에서 나가는 연결을 단계별 탐색 |
make_guidance |
그래프 구조·최근 이력·조건을 묶어 조언 패키지 생성 |
toy_solver |
조언을 소비하는 solver의 경계 |
execute |
결정과 실제 도구 실행을 분리 |
rollout |
행동과 관측을 순서대로 기록 |
toy_refiner |
현재 그래프를 복사해 후보를 만들고 원본 보호 |
gate |
구조 검사와 검증 점수로 채택 여부 결정 |
hops=2는 두 개 노드를 가져오라는 뜻이 아니다. 현재 위치에서 연결을 최대 두 번 따라갈 수 있는 주변 범위를 뜻한다. 분기가 많으면 포함되는 노드 수는 더 커진다.
조건 판정에는 미리 정한 이름을 사용했다. LLM이 만든 조건 문자열을 그대로 eval()하는 방식은 사용하지 않는다. 자유로운 자연어 조건을 사용할 경우 조언용 모델이 해석하게 하되, 실제 도구의 입력 제약은 별도로 검증한다.
5.3 의도적으로 남긴 단순화
predict도구는 단위 검사를 하지 않는 결함 있는 기존 도구를 가정한다. 운영 환경에서는 도구 자체도 표준 단위와 타입을 검사해야 한다.- 현재 상태가 단순하므로 action 이름만으로 위치를 정한다. 여러 실험이 동시에 진행되면 작업 ID와 시편 ID가 필요하다.
- 반복 횟수 제한은 있지만 정교한 시간 제한·재시도·중단 원인 분류는 없다.
- 구조 검사는 모든 조건에서 종료 가능하다는 보장이 아니다.
- 검증 사례는 두 개뿐이다. 단위가 같은 유형의 새 숫자를 통과한 것에 불과하다.
- 실제 LLM 호출 비용·지연·출력 오류는 이 예제에서 측정하지 않는다.
6. 실제 LLM 연결 설계
6.1 모델 호출 경계를 먼저 정한다
특정 공급자의 SDK에 묶기 전에 아래 인터페이스를 정하면 모델을 교체하기 쉽다. 이는 구현 설계이며 완성된 API 클라이언트가 아니다.
| 역할 | 입력 | 출력 |
|---|---|---|
| Guidance | 사용자 목표, 주변 그래프, 최근 이력, 현재 상태 | 당장 확인할 것과 권장 행동을 담은 조언 |
| Solver | 사용자 목표, 실행 이력, 조언, 사용 가능한 도구 | 도구 이름과 인자 또는 최종 답변 |
| Refiner | 현재 그래프, 학습 실행 기록과 점수, 거절 기록 | 추가·삭제·속성 변경 제안 |
| Evaluator | 최종 결과, 정답/평가기준 | 성공 여부, 비용, 오류 유형 |
다음은 연결부의 예시다. 앞 코드 뒤에 둘 수 있지만, complete_json에 실제 모델 호출 함수를 전달해야 실행할 수 있다. 모델 SDK를 생략한 것이며 API 연결까지 검증한 코드는 아니다.
# complete_json(system_prompt: str, payload: dict) -> dict
# 공급자 SDK 호출 및 JSON 파싱은 이 콜백 안에서 구현합니다.
def llm_next_action(graph, active, state, trace, goal, complete_json):
context = {
"goal": goal,
"active": active,
"state": state,
"recent_trace": trace[-3:],
"local_graph": [asdict(e) for e in graph.neighborhood(active, 2)],
}
advice = complete_json(
"절차 지도를 참고해 현재 상황의 조언을 작성하라. "
"기록에 없는 결과를 가정하지 말라. "
"JSON 객체로 immediate_goal, advice, pitfalls를 반환하라.",
context,
)
allowed = {"inspect", "normalize", "predict", "report"}
decision = complete_json(
"목표 달성을 위한 다음 도구 하나를 선택하라. "
"조언을 참고하되 현재 관측으로 판단하라. "
"JSON 객체로 action을 반환하라.",
{"goal": goal, "state": state, "trace": trace,
"guidance": advice, "available_tools": sorted(allowed)},
)
action = decision.get("action")
if action not in allowed:
raise ValueError("등록되지 않은 도구 선택")
return action
이 연결부는 등록된 도구인지 검사하지만 현재 그래프의 인접 노드인지는 강제하지 않는다. 조언과 실행 제약을 구분하기 위해서다. 실제 서비스에서는 각 도구 인자의 타입, 단위, 대상 프로젝트, 실행 가능 상태를 추가 검사한다.
6.2 수정안을 자유 코드 대신 구조화한다
Refiner가 반환할 데이터의 예시는 다음과 같다. 이 JSON은 앞의 교육용 코드가 직접 소비하는 포맷이 아니라, 다음 단계 구현을 위한 제안이다.
{
"base_graph_version": "pg-v1",
"reason": "mm 입력 사례에서 계산 결과가 1000배 커짐",
"evidence_trace_ids": ["tr_mm"],
"operations": [
{"op": "add_node", "id": "normalize"},
{
"op": "delete_edge",
"source": "inspect", "relation": "NEXT", "target": "predict"
},
{
"op": "add_edge",
"source": "inspect", "relation": "IF", "target": "normalize",
"condition": "needs_conversion",
"guidance": "거리를 m로 변환한다",
"pitfalls": "이중 변환을 피한다"
},
{
"op": "add_edge",
"source": "inspect", "relation": "IF", "target": "predict",
"condition": "canonical_units",
"guidance": "표준 단위 입력으로 계산한다",
"pitfalls": "단위 확인을 생략하지 않는다"
},
{
"op": "add_edge",
"source": "normalize", "relation": "NEXT", "target": "predict",
"condition": "always",
"guidance": "변환 결과를 사용한다",
"pitfalls": "원본 수치를 사용하지 않는다"
}
]
}
버전이 맞는 원본의 복사본에만 변경을 적용한다. 노드·도구 참조와 종료 경로를 검사하고, 실행 평가를 통과한 후보에 새 버전을 부여한다. 수정 이유와 근거 이력을 함께 남겨야 연구자가 검토할 수 있다.
“실패했다”는 사실만으로 절차가 잘못됐다고 단정하면 안 된다. 서버 장애, 실험 노이즈, 입력 손상, 도구 버그, 조언 오류를 구분하는 진단 정보가 필요하다. 단위 문제를 발견한 경우에는 그래프 개선과 도구 입력 검증 개선을 함께 고려한다.
7. 소재 분야 응용 아이디어
아래는 연구·개발 후보이며, 효용은 실제 비교 실험으로 확인해야 한다.
7.1 연구 데이터 정제 및 모델 학습 보조
요청 예시: “Load, Distance, Speed로 Wear rate를 예측해줘.”
절차에 담을 수 있는 판단은 다음과 같다.
- 목표값이 마모 체적, 거리당 마모량, 특정 마모율 중 무엇인지 확인한다.
- 하중·거리·속도의 단위를 통일한다.
- 같은 시편의 반복 측정이 학습과 평가에 섞이지 않도록 그룹을 파악한다.
- 전처리 파라미터는 학습 데이터에서 추정한다.
- 성능이 낮으면 타깃 정의, 누수, 데이터 범위를 먼저 점검한다.
- 후보 조합이 데이터 범위에서 벗어나는지 보고한다.
PG의 잠재적 역할: 분석에 필요한 확인 절차를 상황에 따라 제시하고, 반복되는 오류에서 점검 순서를 개선한다. 실제 학습은 기존 모델 학습 도구가 담당한다.
평가: 단위 오류 탐지율, 누수 없는 분할 비율, 재현 가능한 결과 비율, 불필요한 재학습 횟수. MAE만으로 에이전트 품질을 판단하지 않는다.
7.2 Active Learning 실험 운영
요청 예시: “다음에 수행할 조성 후보 다섯 개를 제안해줘.”
PG에는 후보 생성 이후의 운영 판단을 담을 수 있다. 예를 들어 조성 합, 제조 가능 범위, 이미 실행 중인 실험과의 중복, 장비 가용성, 배치 다양성을 확인한 뒤 연구자에게 제안한다.
flowchart TD
A["후보 생성"] --> B["제약과 중복 점검"]
B -->|유효| C["배치 구성"]
B -->|부적합| D["후보 재생성"]
D --> B
C --> E["연구자 검토"]
반복을 허용한다면 최대 재생성 횟수와 후보가 없을 때의 종료 조건도 필요하다. PG가 제안하는 반복과 실행 시스템이 허용하는 반복은 명확히 정의한다.
BO/AL과의 역할 차이: 획득함수는 어떤 후보가 유망한지 수치로 평가한다. PG는 “현재 데이터 품질로 최적화를 진행해도 되는가?”, “후보가 모두 불가능하면 무엇을 바꿀 것인가?” 같은 절차를 조언한다. 둘을 결합하되 수치 최적화 알고리즘을 PG로 대체하지 않는다.
평가: 고정된 총 실험 예산에서 최선 물성, 불가능 후보 비율, 중복 제안 비율, 실제 완료 실험 수를 비교한다. 반복 실험이 필요한 경우에는 중복이 의도적인 재현성 확인인지 구분한다.
7.3 DFT 계산의 실패 진단
요청 예시: “이 계산이 수렴하지 않은 원인을 확인해줘.”
출력 파일과 오류 로그를 읽고, 입력 구조 문제인지, 전자 수렴 문제인지, 자원 문제인지 분류하는 절차를 설계할 수 있다. 실패 원인에 따라 입력 검토·설정 검토·자원 점검으로 갈라지게 한다.
PG의 잠재적 역할: 동일한 실패를 같은 설정으로 반복 제출하는 행동을 줄이고, 수정 전후의 차이를 기록하도록 돕는다.
핵심 설계: 계산이 종료됐다는 것과 물리적으로 타당한 결과를 얻었다는 것을 별도 평가한다. 자동 수정을 허용할 설정 범위와 비교 가능성을 유지할 기준은 계산 목적별로 정한다. 특정 수렴 설정값을 모든 재료에 공통 적용하지 않는다.
평가: 목적에 맞는 유효 결과 비율, 재제출 횟수, 소모 코어 시간, 설정 변경의 추적 가능성.
7.4 MLIP 기반 분자동역학 운영
요청 예시: “이 조성에서 지정된 조건으로 MD를 실행하고 결과를 분석해줘.”
원소 지원 여부와 모델 적용 범위를 확인하고, 작은 예비 실행으로 자원과 구조 상태를 점검한 뒤 본 계산을 진행하는 절차를 만들 수 있다. 메모리 부족, 실행 오류, 비정상 구조 변화 등은 각각 다른 진단 경로로 연결한다.
PG의 잠재적 역할: 무조건 모델이나 시스템 크기를 바꾸기 전에 실제 실패 원인을 확인하도록 안내한다. GPU 메모리 로그, 원자 수, 실행 환경, 모델 버전을 근거로 남긴다.
핵심 설계: 모든 MLIP가 신뢰할 만한 불확실성을 출력한다고 가정하지 않는다. 모델이 제공하는 지표나 별도로 검증한 진단법이 있을 때만 사용한다. 발산을 무조건 시간 간격 문제로 단정하는 절차도 피한다.
평가: 물리적 점검을 통과한 trajectory 비율, 불필요한 재실행 비용, 원인 분류 정확도.
7.5 용접·열처리 공정의 노하우 정리
요청 예시: “이 조건에서 결함이 발생했는데 다음에 무엇을 확인해야 해?”
재료와 두께, 공정 조건, 결함 관찰, 장비 기록을 먼저 확보한 뒤 누락 정보 확인과 추가 측정으로 분기할 수 있다. 단순히 “결함 X → 온도 Y”로 연결하기보다는 적용 조건과 반례를 함께 저장한다.
PG의 잠재적 역할: 숙련자가 확인하는 순서와 판단 근거를 문서화하고, 실패 사례를 통해 빠진 확인 단계를 발견한다.
핵심 설계: 상관관계를 원인으로 바꾸지 않는다. 공정별 전문가 검토를 거친 절차에 적용 범위·근거·버전을 부여한다. 초기에는 공정 변경을 자동 실행하기보다 진단과 제안 품질부터 평가한다.
평가: 누락 정보 탐지, 전문가와의 판단 일치도, 불필요한 추가 실험 수, 원인 후보의 근거 충실도.
7.6 문헌 데이터 추출과 온톨로지 매핑
요청 예시: “논문에서 조성·공정·물성 정보를 추출해 데이터베이스에 넣어줘.”
단위가 없는 값, 표 각주의 예외, 조성 표기 차이, 시편 ID의 불일치처럼 사람이 재검토해야 하는 상황을 절차화한다. 개념 매핑이 불확실하면 원문 위치와 후보 개념을 보존하고 검토 대기 상태로 보낼 수 있다.
PG의 잠재적 역할: 추출 결과를 곧바로 확정하지 않고, 애매한 데이터에 적합한 확인 절차를 선택하게 한다.
평가: 수치·단위·시편 연결의 정확도, 근거 위치 보존율, 잘못 확정된 매핑 비율, 검토자 소요 시간.
8. D3Square·Materials Square 적용 설계
이 절은 특정 제품에 이미 구현된 기능을 설명하는 것이 아니라, 연구 플랫폼에 추가할 수 있는 설계안이다.
8.1 데이터·절차·실행 기록을 연결한다
flowchart TD
A["사용자 연구 목표"] --> B["연구 에이전트"]
C["데이터와 지식그래프"] --> B
D["절차 그래프와 조언"] --> B
B --> E["분석·계산 도구"]
E --> F["실행 기록과 평가"]
F --> G["절차 수정 후보"]
G --> H["검증"]
H -->|채택| D
그림의 피드백 연결은 다음 버전에 대한 개선을 나타낸다. 진행 중인 작업의 그래프 버전은 고정한다.
실행 기록에는 다음 항목을 제안한다.
| 필드 | 목적 |
|---|---|
| run_id, project_id | 실행과 연구 프로젝트 식별 |
| sample_id, experiment_id | 시편·실험 맥락 유지 |
| graph_version | 어떤 절차를 참고했는지 기록 |
| model_id, prompt_version | 에이전트 변경 효과 분리 |
| input_artifact_versions | 입력 데이터 재현 |
| tool_name, tool_version, arguments | 실제 호출 재현 |
| observation, status, error_type | 관측과 실패 원인 보존 |
| guidance, chosen_action | 조언과 최종 선택 비교 |
| score, cost, duration | 효과와 비용 평가 |
전체 원본 데이터를 매번 프롬프트에 넣기보다는, 도구가 계산한 요약과 필요한 근거를 제공하는 방식을 먼저 검토할 수 있다. PG의 절차 ID와 온톨로지의 개념 ID는 연결할 수 있지만, 처음부터 같은 저장 형식을 강제할 필요는 없다.
8.2 첫 구현 범위 제안
개발 범위를 작게 유지하려면 마모 데이터 분석 과정의 단위 확인·목표값 정의·분할 점검·모델 평가를 첫 실험 대상으로 삼을 수 있다. 계산 비용이 큰 시뮬레이션보다 오류를 재현하고 평가하기 쉽도록 설계할 수 있기 때문이다. 이는 프로젝트 우선순위에 대한 제안이며 성능 우위를 보장하는 판단은 아니다.
- 기존 분석 기능 중 입력과 출력이 명확한 도구를 연결한다.
- 10~20개 정도의 절차 노드로 전문가 초안을 만든다. 숫자는 범위 관리용 제안이다.
- 과거 오류를 변형한 합성 사례와 전문가가 검토한 실제 사례를 준비한다.
- 고정 PG를 먼저 평가하고, 효과가 있으면 오프라인 수정 기능을 추가한다.
- 검증을 통과한 버전만 다음 실행에 적용한다.
이 순서라면 “그래프가 있는 것 자체의 효과”와 “경험으로 수정하는 효과”를 나눠 확인할 수 있다.
9. 평가와 실험 계획
9.1 무엇과 비교할 것인가?
| 실험군 | 목적 |
|---|---|
| A: 기존 에이전트 | 기준 성능 확인 |
| B: 동일 지침을 문서로 제공 | 지침 추가 효과 분리 |
| C: 고정 PG + 상황별 조언 | 연결 구조와 조언 효과 확인 |
| D: 개선된 PG + 상황별 조언 | 수정 과정의 추가 효과 확인 |
모델, 도구, 입력, 제한 시간, 평가 규칙을 가능한 한 동일하게 둔다. 조언 생성에는 추가 모델 호출이 들어가므로 호출 횟수·토큰·비용도 기록한다. 같은 비용 예산에서 기존 에이전트의 재시도 횟수를 늘린 비교도 유용하다.
9.2 데이터 분리
- 학습 사례: 실패 진단과 수정 제안에 사용한다.
- 검증 사례: 후보 선택에 사용한다.
- 최종 테스트: 설계를 확정한 후 평가하며 수정 제안에 사용하지 않는다.
소재 데이터에서는 행 단위 무작위 분리만으로 충분하지 않을 수 있다. 같은 시편, 제조 배치, 논문, 실험 장비에서 나온 사례가 여러 분할에 섞이는지 확인한다. 같은 오류 패턴에 숫자만 바꾼 테스트는 구현 확인에는 유용하지만 일반화 증거로는 약하다.
9.3 점수 설계
처음에는 복잡한 종합 점수보다 다음 항목을 각각 보고하는 것이 원인을 파악하기 쉽다.
| 지표 | 예시 |
|---|---|
| 과제 성공 | 요청된 분석을 타당하게 완료 |
| 절차 오류 | 단위 누락, 중복 실행, 확인 생략 |
| 결과 품질 | 오차, 근거 충실도, 전문가 평가 |
| 비용 | 모델 호출비, 계산 자원, 실험 횟수 |
| 지연 | 사용자에게 결과가 나올 때까지의 시간 |
| 실패 복구 | 오류 후 정상 경로로 돌아온 비율 |
여러 목표를 한 점수로 합칠 경우에는 가중치가 의미하는 비용을 명확히 한다. 비용만 줄여 답변을 포기하는 전략이나, 확인 절차만 늘려 성공률을 높이는 전략이 유리해지지 않도록 한다.
9.4 개선 판단의 함정
검증 점수가 한 번 상승했다고 일반화 성능이 반드시 오른 것은 아니다. 작은 검증 집합, 확률적인 모델 출력, 반복적인 후보 탐색으로 우연한 개선이 나타날 수 있다. 반복 실행과 과제별 결과를 보고, 최종 테스트는 분리한다.
실제 실험 실패는 잘못된 절차와 다르다. 의도적으로 불확실한 조성을 실험해 낮은 물성을 얻어도 정보 획득 측면에서는 성공일 수 있다. 물성값과 연구 절차의 품질을 분리해 평가해야 한다.
10. 한계, 확장 과제, 읽기 순서
10.1 자주 생기는 오해
| 오해 | 이 문서에서 취할 해석 |
|---|---|
| PG만 만들면 완전한 에이전트가 된다 | 데이터 접근, 도구, 상태 관리, 평가가 별도로 필요 |
| 그래프가 크면 좋다 | 중복·충돌·불필요한 경로가 많아질 수 있음 |
| 성공 기록은 항상 올바른 절차다 | 우연한 성공이나 평가 허점일 수 있음 |
| 실패는 모두 그래프를 바꿔야 한다 | 도구·데이터·환경 문제일 수 있음 |
| 노하우를 구조화하면 자동으로 과학적 근거가 된다 | 근거와 적용 범위의 검증이 별도로 필요 |
| 그래프가 물리 법칙을 보장한다 | 물리·입력 제약은 계산과 검증으로 확인 |
10.2 실습 확장 과제
- cm 입력 추가: 단위 변환 테이블과 테스트 사례를 확장한다. mm 오류 해결이 cm까지 일반화되는지 확인한다.
- 단위 누락 처리: 추측하지 않고
request_metadata로 이동하는 경로를 추가한다. 미해결 사례를 성공으로 세지 않는다. - 거절 기억 연결: 거절된 변경 내용을 refiner 입력으로 제공하고 같은 실패 후보의 반복률을 측정한다.
- 실제 모델 연결: 가상의 계산식을 학습된 회귀 모델로 교체한다. 에이전트의 평가 데이터와 물성 모델의 학습·평가 데이터 분리를 따로 설계한다.
- LLM 연결: 안내와 행동 선택을 먼저 교체한 뒤, 수작업 refiner를 교체한다. 한 번에 모두 바꾸면 실패 원인을 찾기 어렵다.
- 그래프 버전 비교: 변경 전후 실패 유형, 호출 비용, 재시도 횟수를 표로 남긴다.
10.3 논문 읽기 순서
- 개념과 구조: §3 The Procedural Graph Framework
- 평가 환경과 결과: §4 Experimental Setup, §5 Results
- 구현 세부사항: 원문 목차의 Appendix B와 프롬프트·알고리즘 항목
- 전체 PDF: arXiv PDF
이 문서의 코드로 절차 표현과 검증 흐름을 먼저 실행해 보고, 이후 원문의 실험 조건과 실제 프롬프트를 확인하면 개념과 구현의 차이를 이해하기 쉽다.
출처 및 검증 범위
- Lu et al., Procedural Graphs: Self-Evolving Execution Structures for LLM Agents, arXiv:2609.09153v1, 2026-09-08. 초록 · 본문
- 논문에 대한 사실 요약은 1절에 한정한다. 나머지 코드, 도식, 표, 소재 응용, 평가 계획은 이 문서를 위해 작성한 교육용 설명과 설계 제안이다.
- 전체 실행 코드 블록은 로컬 Python으로 실행해 채택·거절 및 최종 경로를 확인했다. 실제 LLM 연결부는 결정적 콜백으로 인터페이스만 점검했으며 외부 API는 호출하지 않았다.
- 문서의 Mermaid와 수식은 지원하는 Markdown 뷰어에서 렌더링된다. 지원하지 않는 뷰어에서도 코드와 본문은 읽을 수 있다.
함께 나눈 한마디 0
첫 댓글로 이야기를 이어가보세요.
로그인하면 댓글을 남길 수 있어요.