LabHub
배우기 러닝패스 코스

AI Agents — A Graph, Not a Model

Raising the Limit Will Not Finish That Case

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

고리가 있는 그래프를 직접 만들고, 재귀 한도가 무엇을 세는지 자기 손으로 재고, 상한에 닿은 것과 할 일을 마친 것을 다른 결과로 남긴다. 마지막으로 끝내는 조건에 닿지 못하는 입력을 가려내는 진단을 만든다.

왜 중요한가

LangGraph 에서 고리는 특별한 문법이 아니라 그냥 뒤로 가는 엣지다. 그래서 만들기 쉽고, 끝내는 것이 어렵다. 끝내는 조건이 있어도 그 조건에 닿을 수 없는 입력이 하나 섞이면 그 건은 영원히 돈다 — 이 실습의 에이전트가 딱 그렇다. 점수를 깎는 이유가 둘(금칙어·길이)인데 고치는 노드는 하나만 고친다. 재귀 한도는 이럴 때 마지막 방어선이지만, 그것이 세는 단위를 모르면 진단을 잘못한다. 한도가 세는 것은 노드 실행 횟수가 아니라 수퍼스텝이고, 나란히 둔 노드는 몇 개든 한 수퍼스텝이다. 이 실습에서 직접 재어 확인합니다. 그리고 한도에 닿아 예외가 난 것과 그래프가 스스로 끝낸 것은 완전히 다른 사건이다. 같은 칸에 "실패" 로 적어 두면 나중에 무엇이 문제였는지 가를 수 없다. 상한을 두 겹(상태의 roundsrecursion_limit)으로 두고, 안쪽 상한은 결과를 남기게 만든다. 채점기는 여러분이 적어 둔 설명을 믿지 않는다. 여러분의 모듈을 실제로 불러 규칙 함수를 임의의 문장으로 두드려 보고, 그래프를 임의의 한도로 돌려 나온 결과를 채점기가 따로 계산한 값과 대조한다.

단계

  1. /root/work/agroute/route.pyBANNED·MIN_LEN·PASS·make_draft·score_of·revise_text·State·노드 넷(compose·review·revise·shipafter_review·build_graph() 를 만드세요. review 에서 갈리는 조건부 엣지에는 경로 지도를 주고, 이 단계에서는 revise 다음이 END 입니다.
  2. revise 에서 review 로 돌아오는 엣지를 더해 고리를 만드세요. 금칙어가 여러 개인 문의가 여러 바퀴를 돌아 통과해야 합니다.
  3. build_unbounded()run_unbounded(question, limit) 를 더하세요. 끝내는 조건을 일부러 뺀 판을 주어진 한도로 돌리고, 한도에 닿으면 {"error": "GraphRecursionError", "limit": 한도, "message": 예외 메시지} 를 돌려줍니다.
  4. MAX_ROUNDS = 5escalate 노드를 더해 build_graph() 가 어떤 입력에도 예외 없이 끝나게 하세요. 상한에 닿으면 outcomeescalate 입니다.
  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 에 규칙 함수 셋과 노드 넷, after_review, build_graph() 를 만드세요. review 의 조건부 엣지에는 경로 지도 {"ship": "ship", "revise": "revise"} 를 주고, 이 단계에서는 revise 다음이 END 입니다.

조건 함수는 상태를 받아 갈래 이름을 돌려줍니다. 노드 이름을 직접 돌려주게 만들면 그래프 모양을 바꿀 때마다 판단 코드를 고쳐야 합니다. score_of 가 어떻게 점수를 깎는지가 뒤 단계의 재료이니 지시문의 규칙을 정확히 지키세요.

고친 것을 다시 재러 보낸다

revise 에서 review 로 돌아오는 엣지를 더해 고리를 만드세요. 금칙어가 셋인 문의는 세 바퀴를 돌아 ship 으로 가야 합니다.

고리는 특별한 문법이 아니라 그냥 뒤로 가는 엣지입니다. revise 가 금칙어를 한 번에 하나만 지운다는 점이 바퀴 수를 정합니다. 바퀴 수를 세는 roundsrevise 가 올립니다 — 그래야 나중에 안쪽 상한을 걸 자리가 생깁니다.

한도에 닿는 것을 직접 본다

build_unbounded()run_unbounded(question, limit) 를 더하세요. 끝내는 조건을 일부러 뺀 판을 주어진 한도로 돌리고, 한도에 닿으면 {"error": "GraphRecursionError", "limit": 한도, "message": 예외 메시지} 를, 끝났으면 error 를 빈 문자열로 돌려줍니다.

예외는 langgraph.errors.GraphRecursionError 입니다. 메시지에 한도 숫자가 그대로 들어 있으니 지어내지 말고 str(예외) 를 그대로 담으세요. build_unbounded()build_graph()다른 그래프여야 합니다 — 뒤 단계에서 build_graph() 에 상한을 넣어도 이 단계가 계속 유효해야 하기 때문입니다.

결과를 남기는 안쪽 상한

MAX_ROUNDS = 5escalate 노드를 더하고 after_review 가 상한에서 escalate 갈래를 돌려주게 하세요. 경로 지도에도 그 갈래를 더합니다. 이제 build_graph() 는 어떤 입력에도 예외 없이 끝나야 합니다.

바깥 상한(recursion_limit)에 닿으면 예외라 결과가 사라집니다. 안쪽 상한은 결과를 남깁니다 — 그것이 이 단계의 요점입니다. 판단은 after_review 한 곳에만 두세요. 노드 안에서도 끝낼지 보면 두 곳이 어긋나는 날이 옵니다.

한도가 세는 것은 수퍼스텝이다

chain_graph(n)(줄줄이)·fan_graph(n)(나란히)·min_limit(app, state) 를 더하세요. min_limit 은 그 그래프를 끝까지 돌리는 데 필요한 가장 작은 recursion_limit 을 찾아 돌려줍니다.

1 부터 올려 가며 invoke 를 해 보고 처음 성공하는 값을 돌려주면 됩니다. 두 그래프의 답이 어떻게 다른지가 이 단계의 핵심입니다 — 나란히 둔 노드는 몇 개든 같은 수퍼스텝에서 돕니다. 상태는 리듀서가 붙은 열쇠 하나면 충분합니다.

마친 것과 닿은 것을 가른다

run_guarded(question, limit=25) 를 더하세요. 그래프가 스스로 끝났으면 {"status": "done", "rounds": 정수, "outcome": "ship"|"escalate", "score": 정수}, 한도에 닿았으면 {"status": "limit", "rounds": -1, "outcome": "", "score": -1} 입니다.

두 사건을 같은 칸에 "실패" 로 적으면 나중에 가를 수 없습니다. 예외를 잡아 다른 이름을 붙이는 것이 전부이지만, 그 한 줄이 운영에서 크게 갈립니다. 한도를 작게 주면 정상 입력도 limit 이 나옵니다 — 채점기가 그것을 확인합니다.

고쳐도 나아지지 않는 입력을 가려낸다

diagnose(question) 을 더해 {"fixable": 참거짓, "reason": ""|"too_short"|"banned", "best_score": 정수, "rounds_needed": 정수} 를 돌려주게 하세요. best_score 는 금칙어를 더 지울 수 없을 때까지 지운 뒤의 점수입니다.

그래프를 돌리지 않고도 알 수 있습니다 — revise_text 를 더 바뀌지 않을 때까지 적용해 보고 그때의 점수를 재면 됩니다. 그 점수가 PASS 에 못 미치면 이 입력은 아무리 돌려도 통과하지 못합니다. 진단이 한도를 올리는 것보다 값싼 이유가 여기 있습니다.

재어 본 숫자로 보고한다

/root/work/agroute/route_report.jsondefault_recursion_limit·chain_min_limit·fan_min_limit·clean·dirty·stuck·stuck_reason 을, /root/work/agroute/route_report.md## 고리를 어디에 두었나 ## 재귀 한도는 무엇을 세는가 ## 상한에 닿은 것과 마친 것 ## 고쳐도 나아지지 않는 입력 네 절로 쓰세요.

default_recursion_limit 은 config 를 주지 않았을 때의 기본값입니다 — 지어내지 말고 한도를 올려 가며 재어 확인하세요. chain_min_limit·fan_min_limit 은 노드 수를 문자열 열쇠로 둔 객체입니다(예: {"1": ..., "2": ..., "3": ...}). clean·dirty·stuck 은 각각 run_guarded 의 답 그대로 담습니다.