AI 에이전트 — 모델이 아니라 그래프 · 분기와 순환, 그리고 멈추는 조건 · 실습
한도를 올려도 그 건은 안 끝난다
목표
고리가 있는 그래프를 직접 만들고, 재귀 한도가 무엇을 세는지 자기 손으로 재고, 상한에 닿은 것과 할 일을 마친 것을 다른 결과로 남긴다. 마지막으로 끝내는 조건에 닿지 못하는 입력을 가려내는 진단을 만든다.
왜 중요한가
LangGraph 에서 고리는 특별한 문법이 아니라 그냥 뒤로 가는 엣지다. 그래서 만들기 쉽고, 끝내는 것이 어렵다. 끝내는 조건이 있어도 그 조건에 닿을 수 없는 입력이 하나 섞이면 그 건은 영원히 돈다 — 이 실습의 에이전트가 딱 그렇다. 점수를 깎는 이유가 둘(금칙어·길이)인데 고치는 노드는 하나만 고친다.
재귀 한도는 이럴 때 마지막 방어선이지만, 그것이 세는 단위를 모르면 진단을 잘못한다. 한도가 세는 것은 노드 실행 횟수가 아니라 수퍼스텝이고, 나란히 둔 노드는 몇 개든 한 수퍼스텝이다. 이 실습에서 직접 재어 확인합니다.
그리고 한도에 닿아 예외가 난 것과 그래프가 스스로 끝낸 것은 완전히 다른 사건이다. 같은 칸에 "실패" 로 적어 두면 나중에 무엇이 문제였는지 가를 수 없다. 상한을 두 겹(상태의 rounds 와 recursion_limit)으로 두고, 안쪽 상한은 결과를 남기게 만든다.
채점기는 여러분이 적어 둔 설명을 믿지 않는다. 여러분의 모듈을 실제로 불러 규칙 함수를 임의의 문장으로 두드려 보고, 그래프를 임의의 한도로 돌려 나온 결과를 채점기가 따로 계산한 값과 대조한다.
단계
1. /root/work/agroute/route.py 에 BANNED·MIN_LEN·PASS·make_draft·score_of·revise_text·State·노드 넷(compose·review·revise·ship)·after_review·build_graph() 를 만드세요. review 에서 갈리는 조건부 엣지에는 경로 지도를 주고, 이 단계에서는 revise 다음이 END 입니다.
2. revise 에서 review 로 돌아오는 엣지를 더해 고리를 만드세요. 금칙어가 여러 개인 문의가 여러 바퀴를 돌아 통과해야 합니다.
3. build_unbounded() 와 run_unbounded(question, limit) 를 더하세요. 끝내는 조건을 일부러 뺀 판을 주어진 한도로 돌리고, 한도에 닿으면 {"error": "GraphRecursionError", "limit": 한도, "message": 예외 메시지} 를 돌려줍니다.
4. MAX_ROUNDS = 5 와 escalate 노드를 더해 build_graph() 가 어떤 입력에도 예외 없이 끝나게 하세요. 상한에 닿으면 outcome 이 escalate 입니다.
5. chain_graph(n)·fan_graph(n)·min_limit(app, state) 를 더하세요. min_limit 은 그 그래프를 끝까지 돌리는 데 필요한 가장 작은 recursion_limit 을 찾습니다.
6. run_guarded(question, limit) 를 더해 마친 것과 상한에 닿은 것을 다른 status 로 남기세요.
7. diagnose(question) 을 더해 이 입력이 규칙을 만족할 수 있는지, 없다면 왜인지(too_short 또는 banned) 가려내게 하세요.
8. /root/work/agroute/route_report.json 과 /root/work/agroute/route_report.md 에 재어 본 숫자와 판단을 기록하세요.
참고
- 실행 계약: 채점기는
/root/work/agroute/route.py를 파이썬 모듈로 불러 위에 적은 이름들을 직접 씁니다. 스크립트로 실행하지 않습니다. make_draft(question)은question.strip() + " 라고 안내드립니다."입니다.score_of(text)는 100 에서 시작해 금칙어가 나올 때마다 30 을, 길이가MIN_LEN미만이면 40 을 깎고 0 밑으로는 내려가지 않습니다.revise_text(text)는 금칙어를 하나만 지웁니다.- 상수:
BANNED = ("무조건", "절대로", "보장합니다"),MIN_LEN = 30,PASS = 70,MAX_ROUNDS = 5. - 상태 열쇠:
question·draft·score·rounds·outcome·trace.trace는 이어 붙이는 리듀서를 씁니다. - 노드 이름과 상태 열쇠는 겹치면 안 됩니다. LangGraph 는 둘을 같은 이름 공간에 두어서, 초안을 쓰는 노드를
draft라고 부르면 컴파일할 때ValueError: 'draft' is already being used as a state key가 납니다. 그래서 이 실습의 첫 노드 이름은compose입니다. after_review는 갈래 이름을 돌려줍니다(ship·revise·escalate). 어느 갈래가 어느 노드로 가는지는add_conditional_edges의 세 번째 인자가 정합니다.chain_graph(n)·fan_graph(n)은 노드 n 개짜리 그래프를 돌려줍니다. 노드 이름은 자유이고, 각 노드는 자기 이름을 이어 붙이는 열쇠에 하나 씁니다.min_limit(app, state)는 1 부터 올려 가며invoke를 시도해 처음 성공하는 한도를 돌려줍니다. 실패는langgraph.errors.GraphRecursionError로 옵니다.run_guarded의 답: 마쳤으면{"status": "done", "rounds": 정수, "outcome": "ship"|"escalate", "score": 정수}, 한도에 닿았으면{"status": "limit", "rounds": -1, "outcome": "", "score": -1}.diagnose의 답:{"fixable": 참거짓, "reason": ""|"too_short"|"banned", "best_score": 정수, "rounds_needed": 정수}.best_score는 금칙어를 더 지울 수 없을 때까지 지운 뒤의 점수입니다.- 이 파드에는 인터넷이 없습니다. langgraph 0.2.60 이 이미 들어 있습니다.
- 공식 문서: [Use the graph API](https://docs.langchain.com/oss/python/langgraph/use-graph-api) · [GRAPH_RECURSION_LIMIT](https://docs.langchain.com/oss/python/langgraph/GRAPH_RECURSION_LIMIT) · [Graph API overview](https://docs.langchain.com/oss/python/langgraph/graph-api)
- 흔한 실수: 조건 함수가 갈래 이름 대신 노드 이름을 돌려주게 두고 경로 지도를 생략하기, 끝내는 판단을 노드와 조건 함수 두 곳에 두기, 한도를 올려 증상만 넘기기, 나란히 둔 노드가 한도를 먹는다고 믿기.
단계 8개
- 갈래 이름과 경로 지도를 나눈다
- 고친 것을 다시 재러 보낸다
- 한도에 닿는 것을 직접 본다
- 결과를 남기는 안쪽 상한
- 한도가 세는 것은 수퍼스텝이다
- 마친 것과 닿은 것을 가른다
- 고쳐도 나아지지 않는 입력을 가려낸다
- 재어 본 숫자로 보고한다