25. 09. 28.
AI生成コンテンツは不正確または誤解を招く可能性があります。
本記事はFriendliAIとは直接関係ありません。内容はすべて個人的な見解であり、FriendliAIの見解を示すものではありません。
FriendliAIの完全マネージド型モデルサービング製品であるFriendli Dedicated Endpoints(FDE)は、安定的で便利なモデルサービングを提供します。特に関数呼び出し設定が自動的に検出されて有効化される機能をサポートしているため、開発者は別途の複雑な設定なしに簡単にfunction callingを実装できます。
ほとんどの最新モデルは関数呼び出しを標準でサポートしています。Qwen/Qwen3-30B-A3Bモデルを例に、次のような簡単な手順で関数呼び出しが可能なエンドポイントをデプロイできます。
モデルの選択と確認: モデル選択後、Features > Tool call: Supported表示を確認します。 
エンドポイントIDの確認: デプロイが完了したらEndpoint OverviewでEndpoint ID(e.g., dep0hyjaasjus3o)を確認します。 
APIテスト: FRIENDLI_TOKENを発行した後、次のようにAPIリクエストを通じてテストします。
curl https://api.friendli.ai/dedicated/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $FRIENDLI_TOKEN" \
-d '{
"model": "dep0hyjaasjus3o",
"messages": [
{"role": "user", "content": "ソウルの天気を教えて"}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "都市の現在の天気を取得",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string"}
},
"required": ["location"]
}
}
}
],
"tool_choice": "auto"
}' | jq .choices[].message結果の確認: 関数が正常に呼び出されると、次のような応答を受け取ることができます。
{
"content": "\n\n",
"reasoning_content": "\nOkay, the user is asking for the weather in Seoul. Let me check the tools available. There's a function called get_weather that takes a location parameter. Since the user mentioned \"서울\" which is Seoul, I need to call that function with the location set to Seoul. I should make sure the arguments are correctly formatted in JSON. Alright, the required parameter is location, so I'll structure the tool call accordingly.\n",
"role": "assistant",
"tool_calls": [
{
"function": {
"arguments": "{\"location\": \"서울\"}",
"name": "get_weather"
},
"id": "call_XIl0NYYMh9jRLhUdhF2OtTjE",
"type": "function"
}
]
}このように別途の複雑な設定なしでも関数呼び出しが可能なエンドポイントを簡単にデプロイできます。
すべてのモデルが関数呼び出しをサポートしているわけではありません。一部のモデルでは「Tool call: Not supported」メッセージを確認できますが、これは主に以下のような理由によるものです。
Chat Templateの限界: 該当モデルのchat templateにtool callをレンダリングするためのロジックが含まれていない場合です(e.g., google/gemma-3-27b-it)。
エンジン互換性の問題: モデル自体には問題がないものの、エンジンでサポートされていないタイプのツール呼び出し形式を使用している場合です(e.g., zai-org/GLM-4.5-Air)。
しかしこのような制限がFDEで完全にツール呼び出しを使用できないという意味ではありません。FDEはchat templateを基盤にtool callタイプを自動検出して設定する機能を提供するため、ユーザー定義のchat templateを活用すればこのような問題を解決できます。
サポートされていないモデルでも関数呼び出しを可能にする核心は、エンジンでサポートする形式のtool call formatでレンダリングを行うchat templateを作成することです。
以下で説明する機能は高度な機能であり、意図しないモデル関数呼び出し性能の低下を引き起こす可能性があります。
関数呼び出しがサポートされないモデルを解決する核心は、正しいテンプレート変換プロセスを理解することです。このプロセスは大きく2つの段階に分かれます:
まず各モデルの基本構造を把握する必要があります。モデルごとに状況が異なります:
google/gemma-3-27b-itのようなモデルは元々tool callのためのロジックがテンプレートに含まれていません。このような場合は既存のテンプレートをそのまま使用できるため、作業が簡単です。GLM-4.5-Airのようなモデルはすでにtool call機能が実装されていますが、FDEエンジンでサポートしていない形式を使用しています。このような場合は既存のtool callロジックを削除して純粋な会話テンプレートにする必要があります。{{- bos_token -}}
{%- if messages[0]["role"] == "system" -%}
{%- if messages[0]["content"] is string -%}
{%- set first_user_prefix = messages[0]["content"] + "\n\n" -%}
{%- else -%}
{%- set first_user_prefix = messages[0]["content"][0]["text"] + "\n\n" -%}
{%- endif -%}
{%- set loop_messages = messages[1:] -%}
{%- else -%}
{%- set first_user_prefix = "" -%}
{%- set loop_messages = messages -%}
{%- endif -%}
{%- for message in loop_messages -%} {%- if message["role"] == "user" != (loop.index0 % 2 == 0) -%} {{- raise_exception("Conversation roles must alternate user/assistant/user/assistant/...") -}} {%- endif -%} {%- if message["role"] == "assistant" -%} {%- set role = "model" -%} {%- else -%} {%- set role = message["role"] -%} {%- endif -%} {{- "<start_of_turn>" + role + "\n" + (first_user_prefix if loop.first else "") -}} {%- if message["content"] is string -%} {{- message["content"] | trim -}} {%- elif message["content"] is iterable -%} {%- for item in message["content"] -%} {%- if item["type"] == "image" -%} {{- "<start_of_image>" -}} {%- elif item["type"] == "text" -%} {{- item["text"] | trim -}} {%- endif -%} {%- endfor -%} {%- else -%} {{- raise_exception("Invalid content type") -}} {%- endif -%} {{- "<end_of_turn>\n" -}} {%- endfor -%}
{%- if add_generation_prompt -%} {{- "<start_of_turn>model\n" -}} {%- endif -%}
純粋なテンプレートを準備したら、FDEエンジンで認識できる形式でtool call機能を追加する必要があります。最も広く使用されている形式はHermes Function Calling Standard形式です。この形式の主な特徴は以下の通りです:
<tools>...</tools> XMLタグ内にJSON形式で定義<tool_call>{"name": "関数名", "arguments": {...}}</tool_call> 形式で呼び出し<tool_response>...</tool_response> 形式で結果を伝達{{- bos_token -}}
{%- if tools -%}
{%- if messages[0]["role"] == "system" -%}
{%- if messages[0]["content"] is string -%}
{%- set system_content = messages[0]["content"] -%}
{%- else -%}
{%- set system_content = messages[0]["content"][0]["text"] -%}
{%- endif -%}
{%- set first_user_prefix = system_content + "\n\n# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>" -%}
{%- for tool in tools -%}
{%- set first_user_prefix = first_user_prefix + "\n" + (tool | tojson) -%}
{%- endfor -%}
{%- set first_user_prefix = first_user_prefix + "\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{\"name\": <function-name>, \"arguments\": <args-json-object>}\n</tool_call>\n\n" -%}
{%- set loop_messages = messages[1:] -%}
{%- else -%}
{%- set first_user_prefix = "# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>" -%}
{%- for tool in tools -%}
{%- set first_user_prefix = first_user_prefix + "\n" + (tool | tojson) -%}
{%- endfor -%}
{%- set first_user_prefix = first_user_prefix + "\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{\"name\": <function-name>, \"arguments\": <args-json-object>}\n</tool_call>\n\n" -%}
{%- set loop_messages = messages -%}
{%- endif -%}
{%- else -%}
{%- if messages[0]["role"] == "system" -%}
{%- if messages[0]["content"] is string -%}
{%- set first_user_prefix = messages[0]["content"] + "\n\n" -%}
{%- else -%}
{%- set first_user_prefix = messages[0]["content"][0]["text"] + "\n\n" -%}
{%- endif -%}
{%- set loop_messages = messages[1:] -%}
{%- else -%}
{%- set first_user_prefix = "" -%}
{%- set loop_messages = messages -%}
{%- endif -%}
{%- endif -%}
{%- for message in loop_messages -%} {%- if message["role"] == "tool" -%} {%- if loop.first or (loop_messages[loop.index0 - 1]["role"] != "tool") -%} {{- "<start_of_turn>user\n" -}} {%- endif -%} {{- "<tool_response>\n" -}} {%- if message["content"] is string -%} {{- message["content"] | trim -}} {%- else -%} {{- message["content"][0]["text"] | trim -}} {%- endif -%} {{- "\n</tool_response>" -}} {%- if loop.last or (loop_messages[loop.index0 + 1]["role"] != "tool") -%} {{- "<end_of_turn>\n" -}} {%- endif -%} {%- elif message["role"] in ["user", "assistant"] -%} {%- if message["role"] == "user" != ((loop.index0 - (loop_messages[:loop.index0] | selectattr("role", "equalto", "tool") | list | length)) % 2 == 0) -%} {{- raise_exception("Conversation roles must alternate user/assistant/user/assistant/... (excluding tool messages)") -}} {%- endif -%} {%- if message["role"] == "assistant" -%} {%- set role = "model" -%} {%- else -%} {%- set role = message["role"] -%} {%- endif -%} {{- "<start_of_turn>" + role + "\n" + (first_user_prefix if loop.first and role == "user" else "") -}} {%- if message["content"] is string -%} {{- message["content"] | trim -}} {%- elif message["content"] is iterable -%} {%- for item in message["content"] -%} {%- if item["type"] == "image" -%} {{- "<start_of_image>" -}} {%- elif item["type"] == "text" -%} {{- item["text"] | trim -}} {%- endif -%} {%- endfor -%} {%- else -%} {{- raise_exception("Invalid content type") -}} {%- endif -%} {%- if message["role"] == "assistant" and message.get("tool_calls") -%} {%- for tool_call in message["tool_calls"] -%} {{- "\n" -}} {%- if tool_call.get("function") -%} {%- set tool_call = tool_call["function"] -%} {%- endif -%} {{- "<tool_call>\n{\"name\": \"" -}} {{- tool_call["name"] -}} {{- "\", \"arguments\": " -}} {%- if tool_call["arguments"] is string -%} {{- tool_call["arguments"] -}} {%- else -%} {{- tool_call["arguments"] | tojson -}} {%- endif -%} {{- "}\n</tool_call>" -}} {%- endfor -%} {%- endif -%} {{- "<end_of_turn>\n" -}} {%- endif -%} {%- endfor -%}
{%- if add_generation_prompt -%} {{- "<start_of_turn>model\n" -}} {%- endif -%}
修正されたテンプレートを活用して、実際に関数呼び出しがサポートされていなかったモデルをデプロイするプロセスを段階的に見ていきましょう。
モデル状態の確認: まずモデルを選択した後、「Tool call: Not supported」状態であることを確認します。 
カスタムテンプレートの適用: 「Custom chat template」を選択し、作成したchat templateを貼り付けます。 
設定のオーバーライド: 「Override」を選択した後、tool call情報が「unknown」と表示されることを確認します。 
デプロイ完了の確認: デプロイが完了した後、Overviewでtool callが「supported」に変更されたことを確認します。 
APIテスト: 以前と同じ方法でAPI呼び出しを通じてテストします。
curl https://api.friendli.ai/dedicated/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $FRIENDLI_TOKEN" \
-d '{
"model": "depp771mey8y1se",
"messages": [
{"role": "user", "content": "ソウルの天気を教えて"}
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "都市の現在の天気を取得",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string"}
},
"required": ["location"]
}
}
}
],
"tool_choice": "auto"
}' | jq .choices[].message成功結果の確認: これで以前はサポートされていなかったGLM-4.5-Airモデルでも関数呼び出しが正常に動作するようになります:
{
"content": "\n\nソウルの現在の天気を確認いたします。\n",
"reasoning_content": "ユーザーがソウルの天気を教えてほしいと要求しました。私が持っている`get_weather`関数を使用できます。この関数にはlocationパラメータが必要で、ユーザーが「ソウル」という場所を明示的に言及しました。\n\n関数を呼び出す際に「ソウル」をlocationパラメータとして渡す必要があります。",
"role": "assistant",
"tool_calls": [
{
"function": {
"arguments": "{\"location\": \"서울\"}",
"name": "get_weather"
},
"id": "call_oaNZe3tgKLLgv5aUMFQNBTRr",
"type": "function"
}
]
}このような方式を応用すれば、非常に興味深い結果を得ることができます。関数呼び出しの学習をまったく受けていないmistralai/Mistral-7B-Instruct-v0.1のような初期モデルでも、ある程度優れた関数呼び出し性能を示すことを確認できます。
Friendli Dedicated Endpointsはモデルのデプロイから関数呼び出し設定まで全プロセスを簡素化した優れたサービングプラットフォームです。
サポートされているモデルの場合、別途の設定なしでも即座にAgentic workflowを実装でき、開発生産性を大幅に向上させます。サポートされていないモデルの場合でも、Custom Chat Template機能を通じて手動で関数呼び出しを有効化できます。これは単に既存の制約を回避するだけでなく、様々なモデルの潜在能力を最大限に活用できる強力な機能です。
結果的にFDEは関数呼び出しサポートの有無に関係なく、ほぼすべてのモデルでツール呼び出し機能を活用できる柔軟性と拡張性を提供すると言えます。このような特徴はAIエージェント開発と複合的なAIワークフロー構築において非常に大きな価値を提供します。