본문 바로가기
AI2026년 8월 13일6분 읽기

에이전트 툴 호출 — 병렬 호출·에러 복구·툴 남용 방지

YS
김영삼
조회 13
에이전트 툴 호출 — 병렬 호출·에러 복구·툴 남용 방지

에이전트 툴 호출(tool calling)은 LLM이 스스로 판단해 외부 함수(검색, DB 조회, API 호출 등)를 부르고, 그 결과를 받아 다음 행동을 정하는 구조다. 여기서 실무적으로 발이 걸리는 건 세 가지다. 여러 도구를 동시에 부르는 병렬 호출, 도구가 실패했을 때의 에러 복구, 그리고 모델이 도구를 필요 이상으로 남발하거나 반대로 안 쓰는 툴 사용 제어. 이걸 다루는 방식이 에이전트 품질을 좌우한다.

단일 도구 호출은 예제만 봐도 금방 붙인다. 문제는 실제 에이전트 루프를 돌릴 때 생긴다. 도구가 하나 실패하면 전체가 멈추고, 모델이 같은 검색을 세 번 반복하고, 병렬로 부른 결과를 잘못 돌려주니 모델이 이후 병렬 호출을 아예 안 하게 되고… 나는 이 지점들에서 차례로 데였다.

병렬 툴 호출

모델은 한 번의 응답에 여러 개의 도구 호출을 담을 수 있다. 예를 들어 "서울과 부산 날씨"를 물으면 두 도시의 날씨 조회를 동시에 요청한다. 이걸 병렬로 실행하면 지연이 크게 준다. 여기 중요한 규칙이 있다.

여러 도구 호출의 결과는 반드시 하나의 사용자 메시지에 모아서 돌려줘야 한다. 결과를 여러 메시지로 쪼개 보내면, 모델은 "병렬 호출은 하면 안 되나 보다"라고 학습해 이후 병렬 호출을 멈춘다.
# 병렬 호출 처리 (개념)
tool_uses = [b for b in resp.content if b.type == "tool_use"]
results = []
for call in tool_uses:            # 실제론 동시 실행이 이득
    try:
        out = run_tool(call.name, call.input)
        results.append(tool_result(call.id, out))
    except Exception as e:
        # 실패해도 버리지 말고 is_error로 되돌려준다
        results.append(tool_result(call.id, str(e), is_error=True))
# 모든 결과를 '하나의' user 메시지로 한꺼번에 반환
messages.append({"role": "user", "content": results})

에러 복구

도구는 실패한다. 네트워크 오류, 잘못된 인자, 빈 결과. 이때 핵심은 실패를 삼키지 말고 모델에게 알려주는 것이다. 실패한 도구 결과를 is_error 표시와 함께 되돌려주면, 모델은 대개 인자를 고쳐 다시 시도하거나 다른 접근을 택한다. 반대로 실패를 그냥 빠뜨리면, 모델은 그 도구 호출이 어떻게 됐는지 몰라 혼란에 빠진다.

또 하나. 각 도구 결과에는 반드시 대응하는 호출 id를 붙여야 한다. id가 어긋나면 모델은 어떤 요청의 답인지 매칭하지 못한다. 병렬 호출에서 이걸 섞으면 조용히 이상해진다.

툴 남용·과소 사용 제어

모델이 도구를 너무 자주, 혹은 너무 안 쓰는 것도 흔한 문제다. 조절 손잡이는 크게 둘이다.

  • tool_choice: auto(모델이 결정), any(하나는 반드시 사용), 특정 도구 강제, none(사용 금지). 상황에 따라 강제/금지를 건다.
  • 도구 설명(description): "무엇을 하는가"만이 아니라 "언제 부르라"를 명시하면 호출 판단이 정확해진다. 예: "사용자가 현재 가격이나 최신 정보를 물을 때 호출".
증상원인대응
같은 검색 반복결과 활용 실패/루프반복 상한·결과 요약
도구 안 씀호출 조건 불명확설명에 '언제' 명시
도구 과다선택지 너무 많음도구 수 축소

도구가 너무 많으면 모델이 선택에서 헤맨다. 나는 도구 20개를 우겨넣었다가 오히려 엉뚱한 걸 부르는 걸 보고 필요한 것만 남겼다. 정말 많아야 한다면, 매 요청에 전부 노출하지 말고 관련된 것만 동적으로 로드하는 방식을 고려한다.

루프와 종료

에이전트 루프는 "모델 응답 → 도구 실행 → 결과 반환"을 모델이 더는 도구를 안 부를 때까지 반복한다. 무한 루프를 막으려면 최대 반복 횟수 상한을 두는 게 안전하다. SDK가 제공하는 툴 러너(tool runner)를 쓰면 이 루프를 대신 돌려주므로 손으로 짜는 부담이 줄지만, 승인 게이트나 에러 가로채기 같은 개입은 여전히 훅으로 넣을 수 있다.

자주 묻는 질문

병렬 도구 결과를 왜 한 메시지로 묶어야 하나요?

결과를 여러 메시지로 나눠 보내면 모델이 "병렬 호출은 원치 않는 것"으로 해석해 이후 병렬 호출을 줄인다. 한 응답에서 나온 여러 도구 호출의 결과는 하나의 사용자 메시지에 모아 돌려주는 게 규칙이다. 이렇게 해야 병렬 호출 능력이 유지되어 지연 이점을 계속 누린다.

도구가 실패하면 그냥 예외를 던지면 되나요?

안 된다. 실패한 결과도 오류 표시와 함께 모델에게 돌려줘야 한다. 그러면 모델이 인자를 고쳐 재시도하거나 다른 방법을 택한다. 결과를 아예 빠뜨리면 모델은 그 호출의 상태를 알 수 없어 흐름이 꼬인다. 호출 id를 정확히 맞춰 되돌려주는 것도 중요하다.

모델이 도구를 안 쓰게 만들려면요?

tool_choice를 none으로 두면 그 요청에서 도구를 못 쓰게 강제할 수 있다. 반대로 반드시 쓰게 하려면 any나 특정 도구 강제를 쓴다. 다만 상시 강제보다는, 도구 설명에 "언제 호출하라"를 명확히 적어 모델이 스스로 옳게 판단하도록 유도하는 편이 대체로 더 자연스럽다.

툴 러너를 쓰면 제어권을 잃지 않나요?

대체로 그렇지 않다. 대부분의 SDK 툴 러너는 각 턴마다 개입 지점을 제공해서, 도구 실행 전 승인 게이트, 결과 가로채기, 재시도 같은 걸 훅으로 넣을 수 있다. 정말 러너가 노출하지 않는 특수한 제어 흐름이 필요할 때만 직접 루프를 짜면 된다.

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록