본문으로 건너뛰기
페이지 목차
이 페이지에서 검색

멀티 에이전트

이 페이지에 묻기

멀티 에이전트 · 현재 차시와 목차 본문에서만 찾아 답합니다. 답을 지어내지 않고 해당하는 대목을 그대로 보여 주며, 여기에 없으면 없다고 알려 드립니다.

생성형 AI가 아니라 이 페이지 본문을 찾아 그대로 보여 주는 방식입니다. 외부로 나가는 요청이 없습니다.

MULTI · AGENT

멀티 에이전트

생성 수정

도구 실행·검색·기억·그래프 흐름·전문 에이전트의 협업을 구성하고 권한·평가·사람의 확인으로 통제합니다.

26개 문서 · 1일차 · 6 / 26

5차시 · Function Calling 원리

지도 위치 : 1일차 · README 4장 ② 행동 층의 입구 — 모델이 바깥 기능을 요청 한다 | 이전: 4차시 의도 분류기 — 구조화 출력 | 다음: 6차시 데이터 조회 도구 구현 키워드 : 환각, 도구 선언, 호출-실행-반환 흐름

이 차시를 다 읽으면

  1. 환각을 유와 종차로 정의하고, 오피스봇에 도구가 필요한 이유 를 말할 수 있다.
  2. 도구 선언(name · description · input_schema)과 실제 함수를 추상 ↔ 구상(축 ②) , 속성 ↔ 기능(축 ③) 으로 가를 수 있다.
  3. ⭐ "모델은 요청만, 실행은 코드" 를 설명하고, 이것이 세 문장 중 문장 2의 어디에 해당하는지 말할 수 있다.
  4. 요청 → tool_use → 실행 → tool_result → 최종 답 흐름을 그리고, 코드에서 그 자리를 짚을 수 있다.
  5. 병렬 도구 호출, strict, tool_choice의 제약을 알고 AI가 만든 코드에서 틀린 곳을 찾을 수 있다.

0. 이 차시의 판별 축

#축왼쪽 (0)오른쪽 (1)이 축으로 갈리는 것큰 축 (README 5장)
1⭐ 약속인가 부품인가도구 선언 (무엇을 하는 도구인가)실제 함수 (어떻게 하는가)모델이 보는 것 ↔ 코드가 가진 것② 추상 ↔ 구상
2값인가 동작인가선언 · 도구 결과 (데이터)함수 실행 (동작)요청에 실려 가는 글 ↔ 실제로 일어나는 일③ 속성 ↔ 기능
3⭐ 부탁 ↔ 강제모델이 "불러 달라"고 요청코드가 실행 여부 · 방법을 결정누가 바깥 세계를 건드리는가5.4 파생 축 (부모 ②)
4하나 ↔ 여럿한 응답에 도구 호출 하나한 응답에 여러 개 (병렬)결과를 어떻게 돌려보내는가⑤ 하나 ↔ 여럿

이 차시의 질문은 "모델이 모르는 사내 데이터를, 지어내지 않고 답하게 하려면?" 입니다. 앞에서 모델의 출력을 스키마로 묶었습니다(4차시). 도구 호출도 같은 원리입니다 — 모델은 "이 도구를 이 인자로 불러 달라"는 구조화된 요청 을 내고, 실행은 코드가 합니다.


1. 환각과 도구가 필요한 이유

1.1 환각

환각 (Hallucination) — [LLM 출력 오류]인데, [ 근거 없는 내용을 그럴듯한 문장으로 사실처럼 만들어 낸다 ]는 점에서 "모른다"고 답하는 거절이나 단순 오타와 구별된다.

왜 생기는가 — 모델이 하는 일은 "다음에 올 그럴듯한 토큰 고르기" 입니다(3차시). "김민수 님의 남은 연차는"이라는 글 다음에는 숫자가 오는 것이 그럴듯합니다. 모델이 실제 숫자를 모르더라도 말입니다.

1.2 모델이 모르는 것

모델이 모르는 것예모델이 하면 안 되는 일
사내 데이터직원별 연차, IT 티켓 상태그럴듯한 숫자 만들기
실시간 정보오늘 날짜, 지금 시스템 장애 여부학습 시점의 정보로 답하기
사내 문서우리 회사 취업규칙 제23조일반적인 회사 규정으로 대신하기

해법은 두 갈래입니다.

해법무엇을 가져오나이 과정의 자리
도구 호출DB · 시스템에 있는 정형 데이터 (연차 7일, 티켓 상태 "처리 중")⭐ 5~8차시
문서 검색 (RAG)문서에 있는 글 (규정 조항)12~15차시

앞으로 다시 만납니다 — 14차시에서 "근거에 없는 내용 = 환각"이라는 기준으로 답을 검수합니다. 이 차시의 도구 결과도 같은 기준의 근거 가 됩니다.


2. 도구 선언 — 추상과 구상

2.1 정의

Function Calling (도구 호출, Tool Use) — [LLM 출력 방식]인데, [ 글로 답하는 대신 "이 도구를 이 인자로 불러 달라"는 구조화된 요청을 내고, 실제 실행은 앱에 맡긴다 ]는 점에서 글로만 답하는 일반 응답과 구별된다.

도구 선언 — [도구의 명세]인데, [ 이름 · 설명 · 인자 스키마만 적어 모델에게 보내는 것 ]이라는 점에서 실제로 동작하는 함수 코드와 구별된다.

tools = [{
    "name": "get_leave_balance",
    "description": "직원의 남은 연차 일수를 조회한다. 휴가 잔여를 물을 때 사용.",
    "input_schema": {
        "type": "object",
        "properties": {"year": {"type": "integer", "description": "조회 연도"}},
        "required": ["year"],
        "additionalProperties": False,
    },
    "strict": True,
}]

🔍 읽는 포인트 : 세 칸만 보세요.

  • name — 모델이 요청할 때 쓰는 이름
  • description — ⭐ 모델이 이 도구를 언제 고를지 판단하는 근거 . 사실상 프롬프트입니다(6, 7차시)
  • input_schema의 required — 반드시 채워야 하는 인자. 4차시의 스키마와 같은 JSON 스키마입니다

2.2 축 ② 추상 ↔ 구상, 축 ③ 속성 ↔ 기능

def get_leave_balance(employee_id: str, year: int) -> str:   # 실제 함수 (앱 안에만 있음)
    row = db.fetch_leave(employee_id, year)
    return f"{year}년 남은 연차: {row.remaining}일"
도구 선언 (2.1의 tools)실제 함수 (위 코드)
축 ②추상 — 무엇을 하는 도구인가 (약속)구상 — 어떻게 조회하는가 (구현)
축 ③속성 — 요청에 실려 가는 데이터기능 — 실행되는 동작
어디에 있나모델에게 보내는 요청 안 (밖으로 나감, 축 ④)내 서버 안
누가 읽나모델파이썬
틀리면모델이 엉뚱한 도구를 고르거나 인자를 잘못 채움함수가 에러

⭐ 두 가지가 눈에 띕니다. 첫째, 선언에는 year만 있고 함수에는 employee_id가 더 있습니다. 누구의 연차인지는 모델이 정하지 않습니다. 로그인 정보에서 코드가 넣습니다(8차시). 둘째, 같은 선언(약속)을 지키는 한 함수 내부(DB 종류)는 바꿔도 모델은 모릅니다 — 이것이 추상과 구상을 나누는 이유입니다.


3. ⭐ 모델은 요청만, 실행은 코드

3.1 흐름 — 다섯 걸음

① 앱 → 모델   messages + tools=[get_leave_balance 선언]
                "올해 연차 며칠 남았어?"
② 모델 → 앱   stop_reason = "tool_use"
                content = [tool_use 블록 {id: "toolu_01", name: "get_leave_balance", input: {"year": 2026}}]
③ 앱          (실행 여부 판단) → get_leave_balance(세션의 사번, 2026) 실행 → "2026년 남은 연차: 7일"
④ 앱 → 모델   messages + assistant(②의 content 그대로) + user(tool_result: tool_use_id="toolu_01", "…7일")
⑤ 모델 → 앱   stop_reason = "end_turn"   "올해 남은 연차는 7일입니다."

모델을 두 번 부릅니다. ②에서 모델은 답 대신 요청 을 냈고, ⑤에서 도구 결과를 읽고 답합니다. ③에는 모델이 없습니다.

3.2 코드

messages = [{"role": "user", "content": "올해 연차 며칠 남았어?"}]
response = client.messages.create(model=MODEL, max_tokens=16000, tools=tools, messages=messages)

if response.stop_reason == "tool_use":
    messages.append({"role": "assistant", "content": response.content})   # ② 요청을 기록에
    results = []
    for block in response.content:
        if block.type == "tool_use":
            output = run_tool(block.name, block.input, user=current_user)  # ③ 실행은 코드
            results.append({"type": "tool_result", "tool_use_id": block.id, "content": output})
    messages.append({"role": "user", "content": results})                 # ④ 결과를 돌려줌
    response = client.messages.create(model=MODEL, max_tokens=16000, tools=tools, messages=messages)  # ⑤

🔍 읽는 포인트

  • stop_reason == "tool_use" — 모델이 요청했다 는 신호(2차시의 표).
  • messages.append({"role": "assistant", ...}) — 모델의 요청을 먼저 기록하고, 그 뒤에 tool_result를 user로 넣습니다. 둘은 tool_use_id로 묶인 짝 입니다. 앞에서 배운 무상태(2차시) 때문에 이 기록을 다시 보내야 모델이 자기가 무엇을 요청했는지 압니다.
  • block.input — 모델이 채운 인자. dict 로 옵니다. JSON 문자열을 직접 잘라 쓰지 않습니다.
  • run_tool(..., user=current_user) — 누구의 권한으로 실행할지는 코드가 넘깁니다.

3.3 세 문장 중 문장 2 — 모델은 판단하고, 코드는 지킨다

흔한 말실제로 일어난 일
"모델이 DB를 조회했다"❌ 모델은 get_leave_balance를 불러 달라고 요청 했고, 코드가 조회했다
"도구를 선언하면 모델이 함수를 실행한다"❌ 선언은 설명서 . 함수는 내 서버에만 있고 모델은 볼 수도 없다
"모델이 요청하면 반드시 실행된다"❌ 코드는 거절할 수 있다 — 권한이 없으면, 인자가 이상하면, 쓰기 작업이면 사람 확인 후

⭐ ③이 이 과정 전체에서 가장 중요한 자리입니다. 모델의 글이 바깥 세계(사내 DB)에 닿는 유일한 통로 이고, 그 통로의 문지기는 코드 입니다. 그래서 검증(6차시), 권한(8차시), 사람의 승인(23차시)이 모두 ③에 들어갑니다.

비유는 마지막에 — 모델은 주문서를 쓰는 사람 , 코드는 주문서를 보고 창고에서 물건을 꺼내는 직원 입니다. 직원은 주문서가 이상하면 꺼내지 않습니다.


4. 세부 규칙

4.1 병렬 도구 호출

"연차 며칠 남았고, 내 IT 티켓은 어떻게 됐어?" — 모델은 한 응답에 tool_use 블록을 두 개 낼 수 있습니다.

❌⭕
결과마다 user 메시지를 따로 하나씩 추가⭐ 모든 tool_result를 한 user 메시지의 리스트에 담음 (3.2 코드의 results)
첫 번째 tool_use만 처리for block in response.content로 전부 처리

💡 동시에 하나만 부르게 하고 싶다면 tool_choice={"type": "auto", "disable_parallel_tool_use": True} 같은 옵션이 있습니다. 세부는 공식 문서를 확인하세요.

4.2 strict: True

모델이 채운 인자가 input_schema에 정확히 맞도록 보장합니다. 없는 필드를 만들거나 year에 "올해"라는 글을 넣는 일을 막습니다. 4차시의 구조화 출력이 도구 인자에도 적용된 것입니다.

⚠️ 그래도 값이 맞는지 (2026이 사용자가 말한 연도인지)는 보장하지 않습니다. 축 ⑧ — 형식과 뜻은 다른 검사입니다. 값 검증은 6차시에서 코드로 합니다.

4.3 tool_choice — 강제는 안 된다

tool_choice뜻최신 모델 (claude-opus-5-5, claude-sonnet-5-5)
{"type": "auto"} (기본)모델이 쓸지 말지 판단⭕
{"type": "none"}도구를 쓰지 않음⭕
{"type": "any"}아무 도구든 반드시 하나⚠️ 400 에러
{"type": "tool", "name": ...}특정 도구를 반드시⚠️ 400 에러

대응 — auto(기본) + 시스템 프롬프트에 "휴가 잔여를 물으면 반드시 get_leave_balance로 조회하고 추측하지 말라" + strict: True. 그리고 정말 반드시 조회해야 하는 경로라면, 모델에게 고르게 하지 말고 코드가 직접 함수를 부르면 됩니다 — 4차시 분류 결과가 휴가조회일 때 코드가 바로 조회하는 워크플로우가 그 방법입니다.

4.4 도구가 실패하면

tool_result에 "is_error": True를 붙여 돌려줍니다. 모델은 이를 읽고 사용자에게 사정을 알리거나 다른 방법을 시도합니다. ⚠️ 실패를 빈 문자열로 돌려주거나 아예 빼먹으면, 모델은 결과가 없는 채로 답을 지어낼 수 있습니다.

앞으로 다시 만납니다 — is_error의 사용법은 6차시, 실패 종류별 메시지 설계는 8차시에서 다룹니다. 그리고 이 차시의 "요청 → 실행 → 반환"을 한 번이 아니라 모델이 멈출 때까지 반복 하면 9차시의 에이전트 루프가 됩니다.


5. 실습 — 맡기고 검수하기

지시

오피스봇에 첫 도구 get_leave_balance를 붙여서, 도구 호출 한 바퀴를 확인해줘.

참고 공식 문서: Anthropic Tool use 문서(도구 정의, tool_use / tool_result, strict, tool_choice, 병렬 도구 호출)

목표:
1. 가짜 직원 DB(직원 5명, 사번·연도별 남은 연차)를 메모리 dict로
2. 도구 선언은 year 하나만 인자로. employee_id는 선언에 넣지 말고 코드가 현재 사용자로 채울 것
3. tool_use를 받으면 실행 → tool_result(tool_use_id 짝 맞춤) → 재호출 → 최종 답. 반복은 한 바퀴만
4. 병렬 호출 대비: 모든 tool_use를 처리하고 결과를 한 user 메시지에
5. 실행 중 각 단계(stop_reason, tool_use의 name/input, tool_result 내용, 최종 답)를 출력
테스트: ① "올해 연차 며칠 남았어?" ② "작년엔 며칠이었지?" ③ "안녕?" ④ DB에서 조회 실패하도록 만든 경우(is_error)

제약: tool_choice로 특정 도구를 강제하지 말 것(최신 모델은 400). 모델 이름은 상수 하나로.
추측하지 말고 실행 명령과 실행 출력 원문을 붙여.
보고 형식: [시나리오 | 1차 stop_reason | 요청한 도구·인자 | tool_result | 최종 답] 표 + 출력 원문

받을 증거

  • 시나리오 4개의 단계별 출력 원문 (두 번의 호출이 각각 보여야 함)
  • 도구 선언 JSON 원문 — employee_id가 없어야 함

내가 직접 확인할 것

볼 것이걸로 아는 것
①의 최종 답 숫자가 가짜 DB 값과 같은가3.1 — 답이 도구 결과에서 왔는가 (환각이 아닌가)
③에서 도구를 부르지 않았는가2.1 — 설명이 언제 쓸지를 제대로 알려 주는가
②에서 모델이 채운 year가 작년 연도인가4.2 — 형식은 strict가, 값은 모델 판단 (검증은 6차시)
④에서 모델이 실패를 알리는가, 숫자를 지어내는가4.4 — is_error가 환각을 막는가
출력에서 1차 stop_reason이 tool_use, 2차가 end_turn인가3.1 — 흐름이 다섯 걸음대로인가

부록 A. 스스로 확인하기

#질문답이 있는 곳
1환각을 유와 종차로 정의하면? 왜 생기나?1.1
2모델이 모르는 세 가지와 두 해법은?1.2
3도구 선언의 세 칸과, 가장 중요한 칸은?2.1
4도구 선언과 실제 함수를 축 ②와 ③으로 가르면?2.2
5선언에 employee_id가 없는 이유는?2.2
6호출-실행-반환 다섯 걸음과, 모델을 두 번 부르는 이유는?3.1
7tool_use 블록과 tool_result를 묶는 것은?3.2
8"모델은 요청만, 실행은 코드"가 왜 가장 중요한 자리인가?3.3
9병렬 도구 호출의 결과는 어떻게 돌려보내나?4.1
10최신 모델에서 tool_choice로 강제할 수 없을 때의 대응은?4.3

부록 B. 자주 하는 실수

실수바로잡기
"모델이 함수를 실행한다"❌ 모델은 요청만 . 실행은 코드 (3.3)
사번을 도구 인자로 받아 그대로 사용❌ 신원은 세션에서 코드가 (2.2 → 8차시)
assistant 메시지(요청)를 빼고 tool_result만 추가❌ 짝이 깨져 에러. 요청 → 결과 순서 (3.2)
tool_result를 도구마다 user 메시지 따로❌ 한 user 메시지에 모두 (4.1)
block.input을 문자열로 보고 잘라 씀❌ dict로 다룸 (3.2)
tool_choice={"type": "tool", ...}로 강제⚠️ 최신 모델은 400. auto + 프롬프트 + strict, 또는 코드가 직접 호출 (4.3)
도구 실패를 조용히 빈 결과로❌ is_error: True. 안 그러면 지어냄 (4.4)

부록 C. NotebookLM 복습 프롬프트

이 5차시 노트만 근거로 6문제를 내줘. 비전공자 기준으로.
"환각과 도구가 필요한 이유", "도구 선언과 실제 함수(추상↔구상, 속성↔기능)", "모델은 요청만 실행은 코드", "호출-실행-반환 다섯 걸음", "tool_choice 제약"은 반드시 포함해줘.
답은 내가 답한 다음에 채점하고, 틀린 이유를 노트의 절 번호를 근거로 설명해줘.