LabHub
배우기 러닝패스 코스

AI Agents — A Graph, Not a Model

The One Call With Made-Up Arguments

LabHub 에서 이어서 보기

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

목표

도구를 부르는 쪽의 규율을 코드로 적는다. 계약 표를 한 곳에 두고, 고른 인자를 부르기 전에 재고, 돌려받은 결과를 믿지 않고 재고, 실패를 예외가 아니라 값으로 다뤄 사유를 상태에 남기고, 그 사유로 다음 시도를 고치고, 같은 인자로 두 번 부르지 않게 기억한다.

왜 중요한가

도구를 붙인 에이전트가 처음 깨지는 자리는 모델이 아니라 호출 경계다. 모델이 인자 이름을 하나 지어내면 TypeError 가 그래프 밖으로 튀어나와 실행 전체가 끝나고, 상태도 지나온 길도 남지 않는다. 더 나쁜 쪽은 예외가 나지 않는 경우다 — 도구가 빈 목록을 돌려줬는데 그 위에 "재고 3곳" 이라는 문장이 만들어진다. 그래서 이 실습은 계약을 값으로 적는 것에서 시작한다. 이름·인자 스키마·돌려주는 모양·실제 함수를 표 하나에 묶어 두면, 재는 코드가 도구마다 필요 없고 부르는 문이 한 곳으로 좁아진다. 그 문 앞에서 인자를 재고, 문 뒤에서 결과를 재고, 문 앞에 기억을 둔다. 인자를 고르는 자리는 이 실습에서 규칙으로 대신합니다. 실제 에이전트는 그 자리(pick 노드)에서 모델에게 도구 이름과 인자를 묻습니다. 나머지 구조는 똑같습니다 — 오히려 모델이 고를수록 부르기 전 검증이 더 필요합니다. 채점기는 여러분이 적어 둔 설명을 믿지 않습니다. 여러분의 모듈을 실제로 불러 검증 함수를 매번 다른 값으로 두드려 보고, 도구 몸통이 몇 번 돌았는지를 세어 "부르기 전에 막았는가" 를 확인합니다. sku·지역·개수는 실행마다 바뀝니다.

단계

  1. /root/work/agtool/tools.py 에 자료(REGIONS·STOCK)와 MAX_ROWS·CALLS, 도구 둘(stock_lookup·legacy_stock), 계약 표 TOOLS 를 만드세요.
  2. validate_args(name, args) 를 만들어 부르기 전에 인자를 재게 하세요. 통과하면 빈 문자열, 아니면 사유 하나를 돌려줍니다.
  3. call_tool(name, args) 를 만들어 검증을 통과한 호출만 실제로 부르게 하세요. 예외는 밖으로 내지 않고 {"ok", "value", "error"} 로 돌려줍니다.
  4. validate_result(name, value) 를 만들고 call_tool 이 결과도 재게 하세요. 사유는 shape·empty·too_big·range 입니다.
  5. CACHEcache_key(name, args) 를 만들어 같은 도구를 같은 인자로 두 번 부르지 않게 하세요. 성공만 기억합니다.
  6. State 와 노드 넷(pick·fetch·reply·giveup), build_naive()·run_naive(order) 를 만들어 실패를 예외가 아니라 경로로 다루세요.
  7. MAX_ATTEMPTS·REPAIRS·can_repair·repair 노드와 build_graph()·handle(order) 를 더해 사유를 보고 고쳐 다시 시도하게 하세요.
  8. /root/work/agtool/tool_report.json/root/work/agtool/tool_report.md 에 확인한 것을 기록하세요.

참고

계약을 값으로 한 곳에 적는다

/root/work/agtool/tools.pyREGIONS·STOCK·MAX_ROWS·CALLS 와 도구 둘(stock_lookup·legacy_stock), 계약 표 TOOLS 를 만드세요. TOOLS 한 항목은 fn·args·returns 셋을 들고, 두 도구는 첫 줄에서 CALLS 를 1 올립니다.

args 는 인자 이름마다 type·required 와 필요하면 allowed·min·max 를 적은 딕셔너리입니다. returns 에는 돌려주는 열쇠의 형과 rowsmax_len·item 을 적습니다. stock_lookuplimit 개까지만, legacy_stocklimit 을 무시하고 전부 돌려줍니다 — 결과 검증이 필요한 이유를 뒤에서 이 도구로 봅니다. 참고 절의 자료표를 그대로 쓰세요.

부르기 전에 인자를 잰다

validate_args(name, args) 를 만드세요. 계약 표를 읽어 빠진 인자·계약에 없는 인자·형·허용 목록·범위를 재고, 통과하면 빈 문자열을, 아니면 사유 하나(missing:limit 처럼)를 돌려줍니다.

사유 이름은 참고 절에 있는 그대로 쓰세요. 파이썬에서 boolint 의 하위형이라 isinstance(True, int) 가 참입니다 — 개수 자리에 True 가 들어오면 type: 으로 막아야 합니다. 모르는 도구 이름이면 unknown_tool 입니다. 결함이 여럿일 때 무엇을 먼저 돌려줄지는 자유이지만, 채점기는 결함이 하나뿐인 인자만 던집니다.

잘못된 인자로는 부르지 않는다

call_tool(name, args) 를 만드세요. 검증을 통과한 호출만 실제로 부르고, 예외는 밖으로 내지 않습니다. 답은 늘 {"ok": 참거짓, "value": 결과 또는 None, "error": 사유} 이고, 인자 쪽 사유에는 args: 를, 도구가 던진 예외에는 raised: 를 앞에 붙입니다.

실제 함수는 TOOLS[name]["fn"] 에서 꺼내 fn(**args) 로 부릅니다 — 부르는 문을 한 곳으로 좁히는 것이 요점입니다. 검증에서 걸리면 거기서 끝내세요. 채점기는 잘못된 인자를 준 뒤 CALLS 가 그대로인지 봅니다. 부르고 나서 예외를 잡는 방식은 이 검사를 통과하지 못합니다.

돌려준 것을 믿지 않는다

validate_result(name, value) 를 만들고 call_tool 이 결과도 재게 하세요. 사유는 shape·empty·too_big·range 이고, 걸리면 call_toolerrorresult: 를 붙여 돌려줍니다.

계약의 returns 를 읽어 재세요 — 도구마다 손으로 적으면 도구가 늘 때 빠뜨립니다. rows 가 비었으면 empty 입니다(오류가 아니라고 성공으로 넘기면 답을 지어내게 됩니다). max_len 보다 길면 too_big 이고, legacy_stock 이 바로 그 경우를 만듭니다. 행 안의 count 가 계약의 min 보다 작으면 range 입니다.

같은 것을 두 번 묻지 않는다

CACHEcache_key(name, args) 를 만들어 call_tool 이 같은 도구·같은 인자의 결과를 기억하게 하세요. 인자를 적은 순서가 달라도 같은 열쇠가 나와야 하고, 성공만 기억합니다.

json.dumps(args, sort_keys=True, ensure_ascii=False) 를 이름과 이어 붙이면 순서에 흔들리지 않는 열쇠가 됩니다. 기억은 검증을 통과한 뒤, 부르기 직전에 찾아봅니다. 실패까지 기억하면 고쳐서 다시 부를 길이 막히니 성공만 넣으세요. 채점기는 같은 인자로 두 번 불러 CALLS 가 한 번만 올라가는지, 실패한 호출은 두 번 다 몸통이 도는지 봅니다.

실패를 경로로 다룬다

State 와 노드 넷(pick·fetch·reply·giveup), build_naive()·run_naive(order) 를 만드세요. fetch 는 실패해도 예외를 내지 않고 사유를 errors 에 남기며, 실패하면 giveup 으로 갑니다. run_naive{"ok", "answer", "rows", "errors", "calls"} 를 돌려줍니다.

pick 은 주문에서 도구 이름과 인자를 고릅니다 — 실제 에이전트는 이 자리에서 모델에게 묻습니다. errors·calls 에는 이어 붙이는 리듀서를, attempts 에는 더하는 리듀서를 붙이세요. 이 단계의 판은 고치지 않습니다 — 한 번 부르고 실패하면 그대로 포기합니다. replyrows 의 첫 줄로 답 문장을 만들고, giveup 은 마지막 사유를 답에 적습니다.

사유를 보고 고쳐 다시 시도한다

MAX_ATTEMPTS = 3·FALLBACK_REGION·REPAIRS·can_repair(reason)repair 노드, build_graph()·handle(order) 를 더하세요. 고칠 수 있는 사유면 인자나 도구를 고쳐 fetch 로 돌아가고, 고칠 수 없거나 상한에 닿으면 giveup 으로 갑니다.

repairerrors 의 마지막 사유를 읽어 REPAIRS 표대로 고치고 무엇을 고쳤는지 fixes 에 남깁니다. 사유를 상태에 남겨 둔 덕분에 이 자리에서 쓸 수 있습니다 — 예외로 던졌다면 '뭔가 잘못됐다' 밖에 없습니다. 빈 결과는 다시 물어도 빈 결과이니 고칠 수 없는 쪽입니다. handletool_runs 는 부르기 전후의 CALLS 합을 빼서 구하세요.

재어 본 것을 기록한다

/root/work/agtool/tool_report.jsontools·blocked_before_call·bad_result_reasons·memo_second_call_runs·repaired·attempts·gave_up 을, /root/work/agtool/tool_report.md## 도구 계약을 어디에 적었나 ## 부르기 전에 무엇을 막았나 ## 돌려준 결과를 어떻게 믿지 않았나 ## 실패를 어떤 경로로 다뤘나 네 절로 쓰세요.

숫자는 손으로 적지 말고 여러분의 모듈을 실제로 돌려 얻으세요. blocked_before_call 은 잘못된 인자로 불러 본 뒤 도구 몸통이 돈 횟수, bad_result_reasons 는 네 가지 나쁜 결과에 validate_result 가 낸 사유를 정렬한 것, memo_second_call_runs 는 같은 인자로 두 번째 불렀을 때 몸통이 돈 횟수입니다. repaired·attempts 는 지역이 틀린 주문을 handle 에 넘겨 얻고, gave_up 은 빈 결과가 나오는 주문의 ok 를 뒤집은 값입니다. ## 실패를 어떤 경로로 다뤘나 절에는 시도 상한을 숫자로 적으세요.