본문으로 건너뛰기
AIPida
채택됨실전AI 모델·API

OpenAI 함수콜링 → Claude tool use 마이그레이션, 스키마 형식 차이 정리 좀

GPT-4o로 만든 에이전트를 비용/품질 때문에 Claude로 옮기는 중입니다. 함수 정의 부분에서 두 API JSON 형태가 미묘하게 달라서 자꾸 헷갈리네요.

OpenAI는 tools: [{type: "function", function: {name, description, parameters}}] 이런 식이고, 응답도 tool_calls 배열에 arguments문자열로 옵니다.

Claude로 그대로 보내니까 tools.0.input_schema: Field required 에러가 나는데, 두 포맷 매핑 깔끔하게 정리해주실 분 있을까요. 특히 멀티턴에서 tool 결과 돌려주는 부분이 제일 헷갈립니다.

답변 2

  • 채택된 답변에디터 검증

    그 에러(input_schema: Field required) 딱 그거 한 번 맞으면 매핑 감이 옵니다. 둘 다 같은 개념인데 키 이름이랑 nesting만 다릅니다.

    도구 정의

    • OpenAI: {type: "function", function: {name, description, parameters: <JSON Schema>}}
    • Claude: {name, description, input_schema: <JSON Schema>}function 래퍼 없고 parametersinput_schema로 이름만 바뀜. 스키마 본문(type: object, properties, required)은 그대로 복붙해도 됩니다.

    모델이 도구 부를 때

    • OpenAI: message.tool_calls[].function.argumentsJSON 문자열이라 json.loads() 한 번 거쳐야 함
    • Claude: contenttool_use 블록에 .input이미 파싱된 dict로 옵니다. 여기서 실수 자주 하는데, 직렬화된 문자열을 raw로 매칭하지 말고 그냥 .input 쓰세요

    결과 돌려주기 — 여기가 진짜 함정입니다.

    • OpenAI: {role: "tool", tool_call_id, content} 메시지를 그냥 하나 더 append
    • Claude: user 역할 메시지 안에 tool_result 블록으로 넣습니다. role이 tool 아니라 user인 게 헷갈리는 포인트
    messages.append({"role": "assistant", "content": response.content})  # tool_use 블록 통째 보존
    messages.append({"role": "user", "content": [
        {"type": "tool_result", "tool_use_id": block.id, "content": result}
    ]})
    

    제가 마이그레이션하면서 두 번 데인 부분:

    • assistant 응답을 response.content 통째로 다시 append 안 하면 다음 턴에서 tool_use 블록이 빠져서 tool_result가 매칭 안 된다고 400 납니다. text만 뽑아서 넣으면 이 사고 남
    • 한 응답에 tool_use가 여러 개 오면 결과를 한 user 메시지에 다 모아서 돌려줘야 함. 하나씩 따로 append하면 깨져요

    루프 직접 짜기 귀찮으면 Python SDK의 client.beta.messages.tool_runner()(@beta_tool 데코레이터)가 이 왕복을 알아서 처리해줍니다. 근데 처음 한 번은 손으로 짜보는 걸 추천. 위 두 함정을 몸으로 알게 됨.

  • 윗분 답변에 tool_choice도 형태 다른 거 하나 추가합니다.

    • OpenAI: "auto" | "required" | {type: "function", function: {name}}
    • Claude: {type: "auto"} | {type: "any"} | {type: "tool", name} | {type: "none"}

    required{type: "any"} 대응이고, 특정 도구 강제는 {type: "tool", name}. 병렬 호출 끄려면 어느 값이든 disable_parallel_tool_use: true 같이 넣으면 됩니다.

    그리고 잘 안 알려진 거 하나 — 에러 결과 돌려줄 땐 tool_result"is_error": true 붙이세요. 안 붙이면 모델이 실패한 줄 모르고 그 쓰레기값으로 계속 진행합니다. 붙이면 다른 접근 시도해요.