같은 함수가 계속 실행되고 작업이 끝나지 않는다면, 먼저 assistant 메시지와 tool_call_id, 도구 결과, 스트리밍 인자를 원본 그대로 저장해야 합니다. 메시지 체인이 맞는지 확인한 뒤 같은 도구와 같은 인자가 반복되고 결과에 진전이 없을 때만 실제 순환으로 판정하고, 운영 환경에서는 회전 수·시간·비용·부작용 제한을 함께 적용해야 합니다.
이 글은 Kimi K3 API로 도구 호출 에이전트를 만드는 개발자를 위한 안내입니다. 무인 자동화 플랫폼을 운영하거나 발송·결제·주문·데이터 수정처럼 쓰기 작업이 있는 팀이라면 특히 먼저 읽어야 합니다.
시작 전: 화면이 아닌 전체 호출 증거를 저장합니다
반복 현상을 발견하면 대시보드의 마지막 문장만 캡처하지 말고 한 번의 요청과 응답을 하나의 사건으로 보존해야 합니다. 다음 항목을 민감정보를 지운 상태로 저장합니다.
- 요청의
messages배열 - 응답의
finish_reason - 각
tool_call의id - 함수 이름과 완성된 인자 문자열
- 도구 실행 결과와 실행 상태
- 재시도 여부와 외부 요청 식별자
- 요청 식별자와 API 사용량
이 단계에서 세 가지 현상을 분리해야 합니다. 화면이 같은 로그를 중복 표시하는 경우, SDK가 네트워크 재시도로 같은 요청을 다시 보낸 경우, 모델이 실제로 같은 도구를 다시 선택한 경우입니다. 세 현상은 해결 위치가 다릅니다.
발송이나 결제처럼 되돌리기 어려운 도구라면 원인 확인 전까지 읽기 전용 함수나 샌드박스 함수로 바꾸십시오. 실제 고객 데이터에 연결된 상태로 순환을 재현하면 장애 분석이 부작용 사고로 바뀔 수 있습니다.
첫 번째 점검: 메시지 체인을 원형 그대로 되돌립니다
Kimi 공식 도구 호출 흐름에서는 모델이 tool_calls를 반환한 뒤 그 assistant 메시지를 다음 요청의 messages에 추가해야 합니다. 그 다음 각 호출에 대응하는 role=tool 메시지를 넣고, tool_call_id를 원래 호출의 값과 정확히 맞춰야 합니다. 공식 도구 호출 안내와 공식 장애 해결 문서도 이 순서를 핵심 점검 항목으로 설명합니다. (platform.kimi.ai)
정상적인 구조는 다음과 같습니다.
[
{"role": "user", "content": "<질문>"},
{
"role": "assistant",
"tool_calls": [
{
"id": "<호출_ID>",
"type": "function",
"function": {
"name": "<함수_이름>",
"arguments": "{\"item\":\"<값>\"}"
}
}
]
},
{
"role": "tool",
"tool_call_id": "<호출_ID>",
"name": "<함수_이름>",
"content": "{\"status\":\"<결과>\"}"
}
]
특히 다음 두 가지를 확인하십시오.
assistant메시지를 새 객체로 재구성하면서tool_calls를 빠뜨리지 않았는지 확인합니다.- 여러 호출이 한 번에 반환되었다면 모든 호출에 대응하는
role=tool결과가 있는지 확인합니다.
tool_call_id가 맞지 않으면 보통 오류가 발생하지만, 중간 계층이 오류를 숨기고 이전 메시지를 다시 보내면 같은 함수가 반복되는 것처럼 보일 수 있습니다. 따라서 먼저 비스트리밍 요청으로 최소 재현을 실행하십시오. 스트리밍을 끄고 도구 하나만 남겼을 때 정상이라면 모델보다 클라이언트 조립 계층을 먼저 의심해야 합니다.
“Kimi K3가 같은 함수를 부르는 이유”를 메시지 결과에서 찾습니다
메시지 순서가 맞는데도 같은 함수가 반복된다면 도구 결과가 모델에게 새로운 상태를 전달하지 못했을 가능성이 큽니다. 예를 들어 도구는 실패했지만 결과 본문이 매번 "처리 완료"로 반환되거나, 처리 대상의 상태가 바뀌지 않았는데 모델에는 변화가 있다고 알려지는 경우입니다.
다음 기준으로 한 번의 호출을 판정하십시오.
- 도구 이름이 같은가
- 정규화한 인자가 같은가
- 도구 결과의 상태나 대상 값이 변했는가
- 이전 호출 이후 새로운 외부 사건이 발생했는가
인자 비교 전에 JSON 키 순서, 불필요한 공백, 기본값 표현을 정규화해야 합니다. "id":"<값>"과 {"id":"<값>"}처럼 표현만 다른 요청을 서로 다른 호출로 세면 순환을 놓칠 수 있습니다. 반대로 같은 도구라도 대상이나 상태가 실제로 바뀌었다면 정상적인 다단계 실행일 수 있습니다.
공식 문서는 같은 도구, 같은 인자, 연속 반복, 도구 결과의 무진전이 모두 맞을 때 클라이언트 쪽 반복 감지를 적용하라고 안내합니다. 3회·5회·8회 같은 공식 알림 단계는 운영 전체에 강제하는 보편 한도가 아니라 다음 요청에 경고를 덧붙이는 권장 예시입니다. (kimi.com)
두 번째 점검: 스트리밍 인자를 완성된 JSON으로 조립합니다
스트리밍에서는 첫 분할에 호출 ID와 함수 이름이 오고, 뒤의 분할에서 인자 문자열이 이어질 수 있습니다. 여러 도구 호출이 동시에 나오면 index를 기준으로 각 호출의 인자를 따로 이어 붙여야 합니다. Kimi 스트리밍 도구 호출 문서는 이 조립 순서와 delta.tool_calls 확인 방법을 설명합니다. (platform.kimi.ai)
실행 조건은 “인자 조각이 하나 도착했다”가 아니라 “전체 JSON이 파싱되고 검증되었다”여야 합니다.
calls = {}
for chunk in stream:
for delta in (chunk.choices[0].delta.tool_calls or []):
index = delta.index
item = calls.setdefault(index, {
"id": "",
"name": "",
"arguments": ""
})
if delta.id:
item["id"] = delta.id
if delta.function and delta.function.name:
item["name"] = delta.function.name
if delta.function and delta.function.arguments:
item["arguments"] += delta.function.arguments
for item in calls.values():
args = json.loads(item["arguments"])
validate_tool_arguments(item["name"], args)
execute_tool(item["id"], item["name"], args)
다음 실수가 반복 순환의 출발점이 됩니다.
- 모든 분할을 하나의 문자열로 합쳐 여러 호출의 인자가 섞이는 경우
index를 무시해 두 함수의 인자가 뒤섞이는 경우- JSON이 완성되기 전에 도구를 실행하는 경우
- 파싱 실패를 빈 객체나 이전 인자로 조용히 대체하는 경우
- 스트림 재연결 뒤 이전 분할을 다시 붙이는 경우
파서가 오류를 조용히 수정하지 않도록 하십시오. 원본 분할과 최종 조립 결과를 함께 저장해야 어느 단계에서 인자가 변했는지 확인할 수 있습니다. 공식 채팅 API 자료에도 스트리밍 여부와 도구 호출 응답 구조가 정리되어 있습니다. (platform.kimi.ai)
중간 결정표: 어디까지 고치고 언제 차단할지 나눕니다
| 관찰된 상태 | 우선 조치 | 도구 실행 여부 |
|---|---|---|
assistant 메시지가 빠짐 |
반환 메시지를 원본 그대로 추가 | 중단 후 재현 |
tool_call_id가 다름 |
호출 ID와 결과 ID를 대조 | 실행 금지 |
| 스트리밍 JSON이 미완성 | 분할을 index별로 재조립 |
완성 전 금지 |
| 같은 도구와 인자지만 결과가 변함 | 정상 다단계 실행인지 확인 | 위험 도구는 승인 필요 |
| 같은 도구·인자·결과가 반복됨 | 중복 감지와 차단 상태 전환 | 추가 실행 금지 |
| 네트워크 재시도 흔적이 있음 | 요청 식별자와 멱등 키 대조 | 멱등 확인 전 금지 |
이 표에서 마지막 두 줄을 모델의 판단에 맡기면 안 됩니다. 모델에게 “반복하지 마십시오”라고 알려도 클라이언트가 계속 같은 결과를 돌려주거나, 도구 상태가 갱신되지 않으면 순환은 다시 시작될 수 있습니다.
세 번째 단계: 중복 감지와 하드 리밋을 코드에 넣습니다
중복 키는 단순히 함수 이름만 사용하면 안 됩니다. 다음처럼 도구 이름, 정규화 인자, 결과 진행 상태를 함께 묶는 편이 안전합니다.
중복 키 =
도구 이름 + 정규화한 인자 + 마지막으로 확인된 상태값
같은 키가 반복되고 상태값이 바뀌지 않으면 순환 후보로 분류합니다. 그 뒤에는 다음 순서로 처리합니다.
- 추가 도구 실행을 잠급니다.
- 현재 상태를
blocked_no_progress처럼 명확한 값으로 기록합니다. - 모델에 성공으로 위장한 결과를 보내지 않습니다.
- 작업자나 상위 오케스트레이터에 수동 확인을 요청합니다.
- 마지막 안전 지점에서 재개할 수 있도록 체크포인트를 저장합니다.
운영 제한은 네 종류로 분리해야 합니다.
- 회전 제한: 한 작업이 도구 호출을 계속 이어 가지 못하게 합니다.
- 시간 제한: 외부 API가 응답하지 않아도 작업이 남지 않게 합니다.
- 비용 제한: 요청 사용량과 재시도 비용을 감시합니다.
- 부작용 제한: 쓰기 작업은 승인·멱등 키·트랜잭션 경계를 요구합니다.
공식 알림 단계인 3회·5회·8회는 프롬프트 개입의 예시일 뿐입니다. 실제 차단 시점은 정상 작업의 호출 분포와 실패 비용을 기준으로 정해야 합니다. 단순 조회 에이전트와 결제 에이전트에 같은 제한을 적용하면 한쪽은 불필요하게 중단되고 다른 쪽은 위험을 감수하게 됩니다.
첫날 마일스톤: 부작용 있는 도구를 안전하게 바꿉니다
발송, 결제, 주문 생성, 데이터 수정 도구에는 최소한 다음 중 하나를 적용하십시오.
- 요청마다 고유한 멱등 키를 요구합니다.
- 실행 전 확인 단계를 둡니다.
- 외부 변경과 내부 상태 기록을 하나의 트랜잭션 경계로 관리합니다.
- 이미 처리된 키가 들어오면 이전 결과를 반환합니다.
- 실행 전후의 상태를 감사 로그에 남깁니다.
“도구 호출을 몇 회까지 허용할까”라는 질문에는 모든 AI 에이전트에 맞는 고정 답이 없습니다. 정상 샘플을 먼저 수집하고, 정상 완료에 필요한 호출 흐름과 실패 시 손실을 비교한 뒤 작업 유형별 상한을 정해야 합니다. 조회형 작업은 재시도 여지가 있지만, 주문 생성은 한 번의 중복도 사고가 될 수 있습니다.
결정 조건은 다음처럼 단순하게 운영할 수 있습니다.
- 상태가 바뀌고 멱등 키가 확인되면 다음 호출을 허용합니다.
- 상태가 바뀌지만 멱등 키가 없으면 읽기 전용 또는 승인 대기로 되돌립니다.
- 같은 도구와 인자가 반복되고 결과가 변하지 않으면 즉시 차단합니다.
- 네트워크 재시도라면 새 실행을 만들지 말고 기존 요청 상태를 조회합니다.
- 제한에 도달하면 실패를 성공으로 바꾸지 말고 복구 가능한 중단 상태를 반환합니다.
장시간 실행하는 환경에서 로그를 남길 별도 운영 기반이 필요하다면 지속 온라인 맥 환경 안내와 맥 미니 렌탈 안내를 함께 검토할 수 있습니다. 다만 원격 환경을 추가한다고 해서 호출 순환 자체가 해결되는 것은 아닙니다. 차단 로직과 감사 로그가 먼저입니다.
첫 주 마일스톤: 네 가지 샘플로 운영 승인을 받습니다
생산 반영 전에는 다음 네 가지 샘플을 각각 실행하십시오.
| 검증 샘플 | 확인할 사실 | 통과 기준 |
|---|---|---|
| 정상 호출 | 도구 선택과 결과 전달 | 최종 응답까지 도달 |
| 인자 변화 | 같은 함수의 다른 대상 처리 | 정상 호출로 분리 |
| 무진전 반복 | 같은 호출의 재실행 | 차단 상태로 전환 |
| 네트워크 재시도 | 응답 지연과 재전송 | 중복 부작용 없음 |
검증할 때는 모델 응답만 보지 말고 다음 기록을 하나의 증거 사슬로 연결하십시오.
request_id → assistant tool_call_id → 도구 실행 ID → 도구 결과 → API usage → 최종 상태
차단 후에도 백그라운드 작업이 남아 있지 않은지 확인해야 합니다. 큐에 남은 재시도, 타임아웃 뒤 살아 있는 워커, 스트림 재연결 핸들러가 추가 실행을 만들 수 있습니다. 자동화 플랫폼이라면 차단 이벤트에 작업 ID, 마지막 안전 체크포인트, 사람이 재개할 수 있는 경로를 함께 기록하십시오.
현재 방식과 맥 환경을 비교할 때의 운영 판단
로컬 개발 환경에서 짧은 재현만 한다면 지금 쓰는 장비로 충분합니다. 그러나 장시간 회귀 테스트를 실행할 때는 노트북 절전, 네트워크 변경, 터미널 종료, 로그 보존 부족이 장애 원인을 흐릴 수 있습니다. 특히 무인 작업을 밤새 돌리면서 화면 로그만 남기는 방식은 반복 호출과 네트워크 재시도를 구분하기 어렵습니다.
이런 조건이라면 지속 온라인 상태와 로그 보존을 관리하기 쉬운 원격 맥 환경이 더 나은 선택이 될 수 있습니다. 반대로 장기간 고정 부하가 필요하거나 물리 장치 연결, 특수 네트워크, 로컬 보안 정책이 필수라면 직접 장비를 운영하는 편이 낫습니다. 우선 샌드박스에서 한 번 순환을 재현하고, 이후 며칠간 회귀 샘플을 계속 돌려야 할 때 Kvmkit의 맥 미니 렌탈을 비교해 보십시오. 환경을 바꾸는 목적은 모델을 대신 판단하게 만드는 것이 아니라, 호출 기록과 중단 결과를 끊김 없이 남기는 데 있어야 합니다.
M4 Mac mini에서 CI/CD를 돌리면 진짜 편합니다
Xcode, Fastlane, CocoaPods, and SPM are first-class on macOS. Mac mini M4 unified memory keeps signing and archiving smooth; ~4W standby power suits 24/7 build nodes.