Agent는 어떤 도구를 호출해야 할지 어떻게 아는가
Agent 자체는 도구 선택을 하지 않으며, 선택권은 전적으로 LLM에게 있다.
밑바탕의 Function Calling 프로토콜 원리
1단계 · 도구 등록
시스템 기동 시 ToolRegistry가 사용 가능한 모든 도구를 목록으로 등록한다. 각 도구는 반드시 세 가지를 갖춰야 한다:
2단계 · API 요청 구성
사용자가 「베이징 날씨 어때?」라고 물으면, Agent는 대화 이력 + 전량 도구 정의를 함께 LLM 인터페이스의 tools 필드에 밀어 넣는다.
"베이징 날씨 어때?" + 도구 정의 → tools 필드
주의: 이 단계에서 Agent는 전량을 전달하며 어떤 필터링도 하지 않는다. 이것이 바로 「Agent는 선택하지 않는다」의 구체적 표현이다.
3단계 · LLM 분석과 결정
모델은 이를 받은 뒤 스스로 설명을 읽고 스스로 판단해 도구를 호출할지, 어느 것을 호출할지 정한다. 전체 플로우에서 유일한 「선택」 동작이 여기서 발생하며, 그것도 모델 내부에서 발생한다.
4단계 · tool_calls 반환
LLM은 직접 실행하지 않고 tool_calls 배열을 반환한다:
name: "weather",
arguments: {city: "베이징"}
이것이 바로 OpenAI가 정의한 Function Calling 프로토콜이다.
5단계 · 도구 실행
Agent는 tool_calls를 받은 뒤:
- 도구 이름
weather와 파라미터 {city: "베이징"}를 파싱
- ToolRegistry에서
weather에 대응하는 실행기를 찾음
- 파라미터를 넘겨 실행기가 날씨 API를 호출
- 원시 데이터를 회수. 예: 「맑음, 28도」
핵심: 이 결과는 먼저 Agent 계층으로 돌아오며, 사용자에게 바로 가지도 모델에게 바로 가지도 않는다.
6단계 · 결과 회신
Agent는 「맑음, 28도」를 받은 뒤 사용자에게 바로 던지지 않고:
tool 메시지로 포장 → tool_call_id 부착 → 대화 이력에 append
| 역할 | 내용 |
|---|
| 사용자 | "베이징 날씨 어때?" |
| 어시스턴트 | weather 도구를 호출하겠다 |
도구 (role: tool) | 맑음, 28℃ |
tool_call_id의 역할은 이 결과를 방금 그 호출과 대응시키는 것이다.
7단계 · 최종 답변 생성
Agent는 갱신된 완전한 대화 이력을 들고 LLM을 한 번 더 호출한다. 모델은 도구가 반환한 구체적 데이터를 보고 그것을 자연어로 옮긴다:
「베이징은 오늘 맑고 기온은 28도로 야외 활동에 적합합니다.」
마지막으로 Agent를 통해 사용자에게 반환된다. 여기까지 와야 전체 폐루프가 완성된다 — 도구 호출 한 번에 LLM을 두 번 호출한다.
추가 질문 1 · LLM이 존재하지 않는 도구 이름을 반환하면?
표준 답은 「폴백 + 자가 치유」다:
미지의 도구: xxx → tool 메시지로 포장 → 대화 이력에 되넣기
↓
LLM이 에러를 봄 → 자동 수정 ✓
주석: executeTool()이 폴백 → 에러가 메시지로 → LLM 자가 치유. 이 아키텍처에서 에러는 예외가 아니라 모델에게 되먹여지는 또 하나의 컨텍스트다.
추가 질문 2 · 분수령: 가끔 틀림 vs 반복해서 틀림
| 가끔 틀림 ✓ | 반복해서 틀림 ✗ |
|---|
| 판단 | 폴백으로 해결 가능 | 모델이 근본적으로 이해하지 못함 |
| 처리 | 자가 치유 루프에 맡김 | 돌아가서 prompt / 설명을 고쳐야 함 |
반복 오류의 귀인과 올바른 대응
system prompt 또는 도구 설명에 모호함이 있다. 흔한 세 가지 원인:
- 도구 이름이 너무 비슷함
- 설명이 충분히 명확하지 않음
- 선택 범위를 제약하지 않음(「도구 목록에서만 고를 것」을 명시하지 않음)
올바른 대응은 prompt와 설명을 최적화해 모델이 처음부터 제대로 고르게 하는 것이지, 코드 폴백으로 뒷수습하는 것이 아니다.
핵심 통찰
- 선택권을 잘못된 위치에 두는 것이 이런 유형의 시스템 설계 문제에서 가장 흔한 실점 지점이다. 「라우팅 규칙을 짜자」가 감점인 이유는 라우팅을 구현할 수 없어서가 아니라, 모델의 판단권을 없애고 의미 매칭을 키워드 매칭으로 퇴화시키기 때문이다. 「Agent는 X를 어떻게 아는가」 유형의 문제를 만나면 먼저 물을 것: 이 판단은 코드에서 일어나는가 모델에서 일어나는가.
- Agent의 진짜 역할은 「운반공 + 실행기」이지 「두뇌」가 아니다. 7단계 중 Agent가 하는 일은 전부 포장, 파싱, 호출, 래핑, 추가 같은 결정론적 작업이고, 유일한 불확실성은 3단계에 집중되어 있다. 이 분업을 명확히 봐야 이후의 모든 디버깅 전략에 방향이 생긴다 — 불확실한 문제는 설명을 고치고, 확실한 문제는 코드를 고친다.
- 도구 호출 한 번에 LLM을 두 번 호출한다는 점이 비용과 지연을 이해하는 열쇠다. 첫 번째는 무엇을 호출할지 모델이 결정하게 하고, 두 번째는 도구가 반환한 원시 데이터를 사람의 말로 옮기게 한다. 많은 사람이 첫 번째만 기억한다. 이것은 도구가 반환하는 데이터 구조가 최종 답변 품질에 직접 영향을 주는 이유이기도 하다.
- 에러는 예외가 아니라 컨텍스트다.
executeTool()이 「미지의 도구」 에러를 tool 메시지로 포장해 이력에 되넣으면 LLM은 다음 턴에 스스로 치유한다. 이것이 Agent 아키텍처가 전통 프로그램과 가장 다른 지점이다 — 전통 프로그램에서 에러는 위로 던져지고(throw), Agent에서 에러는 앞으로 먹여진다(feed forward). 도구 실행 계층을 설계할 때 에러 메시지의 가독성이 모델의 자기 교정 가능 여부를 직접 결정한다.
- 「가끔」과 「반복」은 완전히 다른 수단으로 처리해야 하며, 둘을 혼동하면 계속 패치만 붙이게 된다. 가끔 틀리는 것은 자가 치유 루프에 맡기고, 반복해서 틀리는 것은 입력(설명, prompt) 자체에 모호함이 있다는 뜻이다. 반복 오류에 계속 폴백 코드를 더하는 것은 공학적 수단으로 의미적 결함을 덮는 것이며, 문제는 쌓이기만 한다.
- 도구 설명은 LLM의 유일한 선택 근거이며, 그 지위는 인터페이스 문서 + 코드와 동등하다. 이름이 너무 비슷함, 설명이 불명확함, 선택 범위 미제약 — 세 원인이 전부 텍스트에 있고 코드 로직에 있는 것은 하나도 없다. 이것이 튜닝의 착수점을 결정한다.
- Function Calling은 벤더를 넘나드는 공통 프로토콜이며, 이는 이식 가능성을 가져다준다. OpenAI가 정의하고 다른 벤더들이 호환한다는 것은,
tool_calls 계층의 코드가 모델 교체 시 기본적으로 다시 쓸 필요가 없다는 뜻이다. 진짜로 다시 조정해야 하는 것은 설명과 prompt이며, 그것이 곧 자동화하기 가장 어려운 부분이다.
엔지니어링 실전 Tips
- 에러 메시지는 모델이 보라고 쓰는 것이다: 「미지의 도구: xxx」가 자가 치유를 발동시킬 수 있는 이유는 어디가 틀렸는지 명확히 말해주기 때문이다. 폴백으로 뭉뚱그린
error나 빈 문자열을 돌려주면 모델은 다음 턴에 같은 실수를 반복할 뿐이다.
tool_call_id는 선택적 장식이 아니다: 한 턴에 여러 개의 tool_calls가 있을 수 있으며, id 대응이 없으면 회신 결과가 뒤바뀔 수 있다.
- 도구 이름끼리 글자 거리를 벌릴 것: 첫 번째로 꼽힌 원인이 「도구 이름이 너무 비슷함」이다. 명명 시
getWeather / getWeatherNow 같은 한 단어 차이 조합을 피할 것.
- system prompt에 선택 범위를 명시적으로 제약할 것: 「주어진 도구 목록에서만 고를 것」이라는 제약의 부재가 환각 도구 이름의 흔한 원인 중 하나다. 이것은 prompt 결함이지 모델 결함이 아니다.
- 등록은 전량 전달이므로 도구가 많아질수록 요청이 비싸진다: 2단계에서 Agent가 전량 도구 정의를 tools 필드에 밀어 넣으므로 매 턴 이 비용을 지불한다. 도구 수의 증가에는 대가가 있다.
- 디버깅 시 문제가 3단계에 있는지 5단계에 있는지 먼저 판정할 것: 모델이 도구를 잘못 고른 것(3단계)이면 설명을 고치고, 도구 실행이 실패한 것(5단계)이면 코드를 고친다. 섞어서 조사하면 양쪽 다 제대로 못 고친다.
- 「반복 오류」를 알람 지표로 삼을 것: 존재하지 않는 같은 도구 이름이 연속으로 나타나면 자가 치유 루프가 무한히 헛돌게 두지 말고 사람이 개입해 prompt를 봐야 한다.