도구 호출 (Function Calling) & 에이전트
모델이 외부 도구(검색·DB·API)를 호출하게 하는 두 가지 방식과, 실제로 부딪히는 함정을 정리합니다.
방식 1 — 네이티브 tool-calling (OpenAI 형식)
지원 모델은 OpenAI tools 형식을 그대로 받습니다.
resp = client.chat.completions.create(
model="qwen2.5-72b",
messages=[{"role": "user", "content": "서울 날씨 알려줘"}],
tools=[{"type": "function", "function": {
"name": "get_weather",
"parameters": {"type": "object", "properties": {"city": {"type": "string"}}}}}],
tool_choice="auto",
)
# resp.choices[0].finish_reason == "tool_calls" 이면 도구 호출 요청
앱은 tool_calls를 파싱 → 실제 함수 실행 → 결과를 role: tool 메시지로 다시 넣고 재호출.
⚠️ 함정: 모델이 도구를 안 부름
- 일부 서빙 설정은 네이티브 tool-choice가 비활성이라
tools를 무시하고 일반 답을 합니다 (서버에서 tool-choice 파서를 켜야 함 — 운영자 영역). - 이럴 땐 아래 프롬프트 ReAct 폴백을 쓰세요.
방식 2 — 프롬프트 기반 ReAct (폴백, 어디서나 동작)
모델에게 "도구를 이 형식으로 요청하라"고 프롬프트로 지시하고, 앱이 파싱·실행·주입합니다.
시스템 프롬프트:
도구가 필요하면 정확히 이 형식으로 한 줄 출력하라: TOOL: name({"arg": "..."})
결과를 받으면 그것을 근거로 답하라.
앱 루프:
1. 모델 호출 → 출력에 TOOL: ... 있으면 파싱.
2. 해당 도구 실행 → 결과를 대화에 주입.
3. 다시 모델 호출 → 최종 답변. (최대 N스텝 제한)
모델 무관·서버 설정 무관하게 동작해서, 도구 지원이 불확실할 때 안전한 1차 경로입니다.
에이전트 패턴 (여러 도구·다단계)
- 1개의 컨트롤러(에이전트) 가 reason ⇄ tool-call ⇄ observe 루프를 돌고, 다른 기능(검색·RAG) 이 그 도구가 됩니다.
- 안전장치: 도구 화이트리스트, 스텝 상한, 각 도구 결과를 신뢰경계 밖으로 취급(인젝션 방어).
파이프라인형 조합 (결정적)
도구 호출이 불안정하면, 고정 파이프라인이 더 안정적입니다. 예: 해석 → (웹)증강 → 추론 → 생성을
순서대로 호출. 재현성·디버깅이 쉽습니다. 유연성이 필요할 때만 에이전트 루프로.
관련: 트러블슈팅 — 도구 호출 · 다국어