結論ファースト: Tool-calling機能を活用することで、LLMを外部システムとシームレスに連携させ、エージェントアプリケーションを構築できます。HolySheep AIは、レート¥1=$1という破格の料金体系(公式比85%節約)、<50msの低レイテンシ、WeChat Pay/Alipay対応など、開発者に優しい環境を提供します。本記事では、関数スキーマの定義からパラメータ検証の実装まで、Hands-onで解説します。
Tool-Calling APIサービス比較
| 項目 | HolySheep AI | OpenAI公式 | Anthropic公式 | Google AI |
|---|---|---|---|---|
| 為替レート | ¥1 = $1(85%節約) | ¥7.3 = $1 | ¥7.3 = $1 | ¥7.3 = $1 |
| GPT-4.1出力 | $8/MTok | $8/MTok | -$ | -$ |
| Claude Sonnet 4.5出力 | $15/MTok | -$ | $15/MTok | -$ |
| Gemini 2.5 Flash出力 | $2.50/MTok | -$ | -$ | $2.50/MTok |
| DeepSeek V3.2出力 | $0.42/MTok | -$ | -$ | -$ |
| レイテンシ | <50ms | 100-300ms | 150-400ms | 80-200ms |
| 決済手段 | WeChat Pay/Alipay/クレカ | クレジットカードのみ | クレジットカードのみ | クレジットカードのみ |
| 無料クレジット | 登録時付与 | $5〜$18 | $5 | $300(90日) |
| 最適なチーム | コスト重視・多言語対応 | OpenAIエコシステム | Anthropicエコシステム | Google Cloud統合 |
Tool-Callingとは
Tool-calling(関数呼び出し)は、LLMがユーザーの意図を理解し、事前に定義した関数を実行する機能です。これにより、データベース検索、API呼び出し、ファイル操作、Webブラウジングなど、現実世界のアクションをLLMに実行させることができます。
私は以前、OpenAIの公式APIでTool-callingを実装していましたが、コスト面で頭を痛めていました。HolySheep AIに移行したところ、同じ品質のまま開発コストを85%削減できました。特に中華圏向けのサービスを開発している場合、WeChat Pay/Alipayでチャージできる点は大きなメリットです。
関数スキーマの定義方法
Tool-callingを実装する第一步は、適切な関数スキーマを定義することです。スキーマはJSON Schema形式で記述し、関数の名前、説明、パラメータ構造を明確に指定します。
スキーマ設計のベストプラクティス
- 明確な関数名: camelCaseまたはsnake_caseで、一目で機能が分かる名前を付ける
- 詳細な説明: LLMがいつこの関数を呼び出すべきかを理解できるよう、descriptionを丁寧に書く
- 必須パラメータの指定: requiredフィールドで必須項目を明示
- 型と制約の定義: enum、minimum、maximumなどで許容値を制限
実践的なコード例
例1:基本的なTool-Calling実装(Python)
import requests
import json
from typing import List, Dict, Any, Optional
class HolySheepToolCaller:
"""HolySheep AI API を使用したTool-calling実装"""
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = "https://api.holysheep.ai/v1"
self.headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
def define_functions(self) -> List[Dict[str, Any]]:
"""関数スキーマの定義"""
return [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "指定された都市の現在の天気を取得します。天気予報の確認や旅行計画に最適です。",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "天気を知りたい都市名(例:東京、ニューヨーク)",
"minLength": 1,
"maxLength": 50
},
"unit": {
"type": "string",
"description": "温度の単位",
"enum": ["celsius", "fahrenheit"],
"default": "celsius"
}
},
"required": ["city"]
}
}
},
{
"type": "function",
"function": {
"name": "calculate_tip",
"description": "レストランでのチップ額を計算します。割勘や多人数の計算に便利です。",
"parameters": {
"type": "object",
"properties": {
"amount": {
"type": "number",
"description": "請求総額(日本円)",
"minimum": 0,
"maximum": 1000000
},
"percentage": {
"type": "number",
"description": "チップの割合(%)",
"minimum": 0,
"maximum": 100,
"default": 15
},
"people": {
"type": "integer",
"description": "分割人数",
"minimum": 1,
"maximum": 100,
"default": 1
}
},
"required": ["amount"]
}
}
}
]
def execute_tool(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
"""ツールの実処理を実行"""
if tool_name == "get_weather":
return self._get_weather(arguments["city"], arguments.get("unit", "celsius"))
elif tool_name == "calculate_tip":
return self._calculate_tip(
arguments["amount"],
arguments.get("percentage", 15),
arguments.get("people", 1)
)
else:
return {"error": f"Unknown tool: {tool_name}"}
def _get_weather(self, city: str, unit: str) -> Dict[str, Any]:
"""天気取得の実装(ダミー)"""
weather_conditions = ["晴れ", "曇り", "雨", "雪", "快晴"]
import random
temp = random.randint(15, 30)
if unit == "fahrenheit":
temp = int(temp * 9/5 + 32)
return {
"city": city,
"weather": random.choice(weather_conditions),
"temperature": temp,
"unit": unit
}
def _calculate_tip(self, amount: float, percentage: float, people: int) -> Dict[str, Any]:
"""チップ計算の実装"""
tip_amount = amount * (percentage / 100)
total = amount + tip_amount
per_person = total / people
return {
"original_amount": amount,
"tip_percentage": percentage,
"tip_amount": round(tip_amount, 2),
"total": round(total, 2),
"split_people": people,
"per_person": round(per_person, 2)
}
def chat(self, message: str) -> Dict[str, Any]:
"""Chat Completions API呼び出し"""
payload = {
"model": "gpt-4.1",
"messages": [{"role": "user", "content": message}],
"tools": self.define_functions(),
"tool_choice": "auto"
}
response = requests.post(
f"{self.base_url}/chat/completions",
headers=self.headers,
json=payload
)
if response.status_code != 200:
raise Exception(f"API Error: {response.status_code} - {response.text}")
result = response.json()
# Tool-callが要求された場合
if result.get("choices")[0].get("message").get("tool_calls"):
tool_calls = result["choices"][0]["message"]["tool_calls"]
tool_results = []
for tool_call in tool_calls:
tool_name = tool_call["function"]["name"]
arguments = json.loads(tool_call["function"]["arguments"])
# パラメータ検証
validation_error = self._validate_parameters(tool_name, arguments)
if validation_error:
tool_results.append({
"tool_call_id": tool_call["id"],
"role": "tool",
"content": json.dumps({"error": validation_error})
})
continue
# ツール実行
result_data = self.execute_tool(tool_name, arguments)
tool_results.append({
"tool_call_id": tool_call["id"],
"role": "tool",
"content": json.dumps(result_data)
})
return {"tool_results": tool_results, "original": result}
return result
def _validate_parameters(self, tool_name: str, arguments: Dict[str, Any]) -> Optional[str]:
"""パラメータ検証"""
functions = self.define_functions()
schema = None
for func in functions:
if func["function"]["name"] == tool_name:
schema = func["function"]["parameters"]
break
if not schema:
return f"Unknown function: {tool_name}"
# 必須パラメータチェック
required = schema.get("required", [])
for req_param in required:
if req_param not in arguments:
return f"Missing required parameter: {req_param}"
# 型と制約チェック
properties = schema.get("properties", {})
for param_name, param_spec in properties.items():
if param_name in arguments:
value = arguments[param_name]
param_type = param_spec.get("type")
# 型チェック
if param_type == "string" and not isinstance(value, str):
return f"Parameter '{param_name}' must be a string"
elif param_type == "number" and not isinstance(value, (int, float)):
return f"Parameter '{param_name}' must be a number"
elif param_type == "integer" and not isinstance(value, int):
return f"Parameter '{param_name}' must be an integer"
# 文字列長チェック
if param_type == "string":
if "minLength" in param_spec and len(value) < param_spec["minLength"]:
return f"Parameter '{param_name}' must be at least {param_spec['minLength']} characters"
if "maxLength" in param_spec and len(value) > param_spec["maxLength"]:
return f"Parameter '{param_name}' must be at most {param_spec['maxLength']} characters"
# 数値範囲チェック
if param_type in ["number", "integer"]:
if "minimum" in param_spec and value < param_spec["minimum"]:
return f"Parameter '{param_name}' must be at least {param_spec['minimum']}"
if "maximum" in param_spec and value > param_spec["maximum"]:
return f"Parameter '{param_name}' must be at most {param_spec['maximum']}"
# enumチェック
if "enum" in param_spec and value not in param_spec["enum"]:
return f"Parameter '{param_name}' must be one of: {param_spec['enum']}"
return None
使用例
if __name__ == "__main__":
caller = HolySheepToolCaller(api_key="YOUR_HOLYSHEEP_API_KEY")
# 天気查询
result = caller.chat("東京、今の天気教えて")
print("天気查询結果:", result)
# チップ計算
result = caller.chat("5000円の請求で15%チップ、一人暮らしだけど何人で行けばいい?")
print("チップ計算結果:", result)
例2:Node.jsでのTool-Calling実装
import https from 'https';
/**
* HolySheep AI API - Tool-Calling クライアント
* パラメータ検証を備えた堅牢な実装
*/
class HolySheepToolCaller {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseUrl = 'https://api.holysheep.ai/v1';
}
/**
* APIリクエストを実行
*/
async request(endpoint, method, payload) {
return new Promise((resolve, reject) => {
const url = new URL(${this.baseUrl}${endpoint});
const options = {
hostname: url.hostname,
port: 443,
path: url.pathname,
method: method,
headers: {
'Authorization': Bearer ${this.apiKey},
'Content-Type': 'application/json'
}
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try {
const parsed = JSON.parse(data);
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve(parsed);
} else {
reject(new Error(API Error: ${res.statusCode} - ${JSON.stringify(parsed)}));
}
} catch (e) {
reject(new Error(Parse Error: ${data}));
}
});
});
req.on('error', reject);
req.write(JSON.stringify(payload));
req.end();
});
}
/**
* 関数スキーマ定義
*/
getTools() {
return [
{
type: 'function',
function: {
name: 'search_products',
description: 'ECサイトを検索して商品を取得します。在庫確認や価格比較に便利です。',
parameters: {
type: 'object',
properties: {
query: {
type: 'string',
description: '検索キーワード',
minLength: 1,
maxLength: 100
},
category: {
type: 'string',
description: '商品カテゴリ',
enum: ['electronics', 'clothing', 'food', 'books', 'home']
},
max_price: {
type: 'number',
description: '最大価格(日本円)',
minimum: 0,
maximum: 1000000
},
min_price: {
type: 'number',
description: '最小価格(日本円)',
minimum: 0,
maximum: 1000000
},
limit: {
type: 'integer',
description: '取得件数(1-50)',
minimum: 1,
maximum: 50,
default: 10
}
},
required: ['query']
}
}
},
{
type: 'function',
function: {
name: 'convert_currency',
description: '通貨両替額を計算します。旅行や国際取引に役立ちます。',
parameters: {
type: 'object',
properties: {
amount: {
type: 'number',
description: '金額',
minimum: 0.01
},
from_currency: {
type: 'string',
description: '変換元通貨コード',
enum: ['JPY', 'USD', 'CNY', 'EUR', 'GBP', 'KRW']
},
to_currency: {
type: 'string',
description: '変換先通貨コード',
enum: ['JPY', 'USD', 'CNY', 'EUR', 'GBP', 'KRW']
}
},
required: ['amount', 'from_currency', 'to_currency']
}
}
}
];
}
/**
* パラメータ検証
*/
validateParameters(toolName, args) {
const tools = this.getTools();
const tool = tools.find(t => t.function.name === toolName);
if (!tool) {
return { valid: false, error: Unknown tool: ${toolName} };
}
const schema = tool.function.parameters;
const { properties, required = [] } = schema;
// 必須パラメータチェック
for (const param of required) {
if (args[param] === undefined || args[param] === null) {
return { valid: false, error: Missing required parameter: ${param} };
}
}
// 各パラメータの検証
for (const [key, value] of Object.entries(args)) {
if (!properties[key]) {
return { valid: false, error: Unknown parameter: ${key} };
}
const spec = properties[key];
// 型チェック
if (spec.type === 'string' && typeof value !== 'string') {
return { valid: false, error: Parameter '${key}' must be a string };
}
if (spec.type === 'number' && typeof value !== 'number') {
return { valid: false, error: Parameter '${key}' must be a number };
}
if (spec.type === 'integer' && !Number.isInteger(value)) {
return { valid: false, error: Parameter '${key}' must be an integer };
}
// 文字列制約
if (spec.type === 'string') {
if (spec.minLength && value.length < spec.minLength) {
return { valid: false, error: Parameter '${key}' must be at least ${spec.minLength} characters };
}
if (spec.maxLength && value.length > spec.maxLength) {
return { valid: false, error: Parameter '${key}' must be at most ${spec.maxLength} characters };
}
}
// 数値制約
if (spec.type === 'number' || spec.type === 'integer') {
if (spec.minimum !== undefined && value < spec.minimum) {
return { valid: false, error: Parameter '${key}' must be at least ${spec.minimum} };
}
if (spec.maximum !== undefined && value > spec.maximum) {
return { valid: false, error: Parameter '${key}' must be at most ${spec.maximum} };
}
}
// 列挙値チェック
if (spec.enum && !spec.enum.includes(value)) {
return { valid: false, error: Parameter '${key}' must be one of: ${spec.enum.join(', ')} };
}
}
// 追加のビジネスロジック検証
if (toolName === 'search_products') {
if (args.min_price && args.max_price && args.min_price > args.max_price) {
return { valid: false, error: 'min_price cannot be greater than max_price' };
}
}
if (toolName === 'convert_currency') {
if (args.from_currency === args.to_currency) {
return { valid: false, error: 'from_currency and to_currency must be different' };
}
}
return { valid: true };
}
/**
* ツール実行
*/
async executeTool(toolName, args) {
console.log(Executing tool: ${toolName} with args:, args);
switch (toolName) {
case 'search_products':
return this.searchProducts(args);
case 'convert_currency':
return this.convertCurrency(args);
default:
throw new Error(Unknown tool: ${toolName});
}
}
/**
* 商品検索の実装
*/
searchProducts({ query, category, max_price, min_price, limit = 10 }) {
// ダミー実装
const products = [
{ id: 1, name: 'ワイヤレスヘッドフォン', price: 15000, category: 'electronics' },
{ id: 2, name: '棉T恤', price: 3500, category: 'clothing' },
{ id: 3, name: 'オーガニック紅茶', price: 2500, category: 'food' },
{ id: 4, name: 'TypeScript入門', price: 2800, category: 'books' },
{ id: 5, name: '調節可能デスク', price: 45000, category: 'home' },
];
let results = products.filter(p => {
const matchQuery = p.name.includes(query);
const matchCategory = !category || p.category === category;
const matchMaxPrice = !max_price || p.price <= max_price;
const matchMinPrice = !min_price || p.price >= min_price;
return matchQuery && matchCategory && matchMaxPrice && matchMinPrice;
});
return results.slice(0, limit);
}
/**
* 通貨変換の実装
*/
convertCurrency({ amount, from_currency, to_currency }) {
// ダミー為替レート
const rates = {
'JPY': { 'USD': 0.0067, 'CNY': 0.048, 'EUR': 0.0061, 'GBP': 0.0053, 'KRW': 8.9 },
'USD': { 'JPY': 149.5, 'CNY': 7.2, 'EUR': 0.92, 'GBP': 0.79, 'KRW': 1330 },
'CNY': { 'JPY': 20.8, 'USD': 0.14, 'EUR': 0.13, 'GBP': 0.11, 'KRW': 185 },
'EUR': { 'JPY': 163, 'USD': 1.09, 'CNY': 7.8, 'GBP': 0.86, 'KRW': 1450 },
'GBP': { 'JPY': 190, 'USD': 1.27, 'CNY': 9.1, 'EUR': 1.16, 'KRW': 1690 },
'KRW': { 'JPY': 0.11, 'USD': 0.00075, 'CNY': 0.0054, 'EUR': 0.00069, 'GBP': 0.00059 }
};
const rate = rates[from_currency]?.[to_currency];
if (!rate) {
throw new Error(Exchange rate not available: ${from_currency} to ${to_currency});
}
const converted = amount * rate;
return {
original: { amount, currency: from_currency },
converted: { amount: converted.toFixed(2), currency: to_currency },
rate: rate
};
}
/**
* チャット実行
*/
async chat(message) {
const payload = {
model: 'gpt-4.1',
messages: [{ role: 'user', content: message }],
tools: this.getTools(),
tool_choice: 'auto'
};
const response = await this.request('/chat/completions', 'POST', payload);
const choice = response.choices?.[0];
if (!choice?.message?.tool_calls) {
return { reply: choice?.message?.content };
}
// ツール呼び出しの処理
const toolResults = [];
for (const toolCall of choice.message.tool_calls) {
const toolName = toolCall.function.name;
let args;
try {
args = JSON.parse(toolCall.function.arguments);
} catch (e) {
toolResults.push({
tool_call_id: toolCall.id,
role: 'tool',
content: JSON.stringify({ error: 'Invalid JSON arguments' })
});
continue;
}
// パラメータ検証
const validation = this.validateParameters(toolName, args);
if (!validation.valid) {
toolResults.push({
tool_call_id: toolCall.id,
role: 'tool',
content: JSON.stringify({ error: validation.error })
});
continue;
}
// ツール実行
try {
const result = await this.executeTool(toolName, args);
toolResults.push({
tool_call_id: toolCall.id,
role: 'tool',
content: JSON.stringify(result)
});
} catch (e) {
toolResults.push({
tool_call_id: toolCall.id,
role: 'tool',
content: JSON.stringify({ error: e.message })
});
}
}
return {
tool_calls: choice.message.tool_calls,
tool_results: toolResults
};
}
}
// 使用例
async function main() {
const caller = new HolySheepToolCaller('YOUR_HOLYSHEEP_API_KEY');
try {
// 商品検索
const result1 = await caller.chat('ワイヤレスヘッドフォン探してる、5万円以下のやつ');
console.log('商品検索結果:', JSON.stringify(result1, null, 2));
// 通貨変換
const result2 = await caller.chat('100ドルって日本円でいくら?');
console.log('通貨変換結果:', JSON.stringify(result2, null, 2));
} catch (error) {
console.error('Error:', error.message);
}
}
main();
パラメータ検証の実装パターン
スキーマレベルでの検証
関数スキーマ自体に制約を定義することで、LLMが最初から正しいパラメータを生成する可能性を高められます。以下の点是に注意してください:
- required配列の適切な使用: 必須パラメータのみをここに含め、オプションはデフォルト値を設定
- enumの活用: 取り得る値が限られている場合は、enumを使用してタイプミスを防止
- 数値制約の明示: minimum/maximumで許容範囲を明確に指定
- descriptionの丁寧さ: 各パラメータの役割と期待値を詳細に説明
アプリケーションレベルでの検証
LLMは完璧ではないため、APIレベルでの検証は不可欠です。以下の多層防御を実装することを推奨します:
# 検証の多層防御パターン
class ValidationLayer:
"""パラメータ検証の多層防御"""
def __init__(self):
self.error_messages = []
def validate(self, tool_name: str, args: dict, schema: dict) -> tuple[bool, list]:
"""
パラメータ検証のメインエントリーポイント
Returns: (is_valid, error_messages)
"""
self.error_messages = []
# ステップ1: 必須パラメータの存在確認
self._check_required_fields(args, schema)
# ステップ2: 型チェック
self._check_types(args, schema)
# ステップ3: 範囲チェック(数値・文字列長)
self._check_ranges(args, schema)
# ステップ4: 列挙値の検証
self._check_enums(args, schema)
# ステップ5: ビジネスロジック検証
self._check_business_rules(tool_name, args)
return len(self.error_messages) == 0, self.error_messages
def _check_required_fields(self, args: dict, schema: dict):
"""必須フィールドの存在確認"""
required = schema.get("required", [])
for field in required:
if field not in args or args[field] is None:
self.error_messages.append(f"必須パラメータ '{field}' が不足しています")
def _check_types(self, args: dict, schema: dict):
"""型チェック"""
properties = schema.get("properties", {})
for key, value in args.items():
if key not in properties:
continue
spec = properties[key]
expected_type = spec.get("type")
if expected_type == "string" and not isinstance(value, str):
self.error_messages.append(f"'{key}' は文字列である必要があります")
elif expected_type == "number" and not isinstance(value, (int, float)):
self.error_messages.append(f"'{key}' は数値である必要があります")
elif expected_type == "integer" and not isinstance(value, int):
self.error_messages.append(f"'{key}' は整数である必要があります")
elif expected_type == "boolean" and not isinstance(value, bool):
self.error_messages.append(f"'{key}' は真偽値である必要があります")
elif expected_type == "array" and not isinstance(value, list):
self.error_messages.append(f"'{key}' は配列である必要があります")
elif expected_type == "object" and not isinstance(value, dict):
self.error_messages.append(f"'{key}' はオブジェクトである必要があります")
def _check_ranges(self, args: dict, schema: dict):
"""範囲チェック"""
properties = schema.get("properties", {})
for key, value in args.items():
if key not in properties:
continue
spec = properties[key]
# 数値範囲
if isinstance(value, (int, float)):
if "minimum" in spec and value < spec["minimum"]:
self.error_messages.append(f"'{key}' は {spec['minimum']} 以上の値が必要です")
if "maximum" in spec and value > spec["maximum"]:
self.error_messages.append(f"'{key}' は {spec['maximum']} 以下の値が必要です")
# 文字列長
if isinstance(value, str):
if "minLength" in spec and len(value) < spec["minLength"]:
self.error_messages.append(f"'{key}' は {spec['minLength']} 文字以上必要です")
if "maxLength" in spec and len(value) > spec["maxLength"]:
self.error_messages.append(f"'{key}' は {spec['maxLength']} 文字以下にしてください")
# 配列長
if isinstance(value, list):
if "minItems" in spec and len(value) < spec["minItems"]:
self.error_messages.append(f"'{key}' は {spec['minItems']} 個以上の要素が必要です")
if "maxItems" in spec and len(value) > spec["maxItems"]:
self.error_messages.append(f"'{key}' は {spec['maxItems']} 個以下にしてください")
def _check_enums(self, args: dict, schema: dict):
"""列挙値チェック"""
properties = schema.get("properties", {})
for key, value in args.items():
if key not in properties:
continue
spec = properties[key]
if "enum" in spec and value not in spec["enum"]:
self.error_messages.append(
f"'{key}' は次のいずれかの値である必要があります: {', '.join(map(str, spec['enum']))}"
)
def _check_business_rules(self, tool_name: str, args: dict):
"""ビジネスロジック検証"""
if tool_name == "search_products":
if "min_price" in args and "max_price" in args:
if args["min_price"] > args["max_price"]:
self.error_messages.append("最小価格は最大価格以下である必要があります")
elif tool_name == "booking":
if "start_date" in args and "end_date" in args:
if args["start_date"] > args["end_date"]:
self.error_messages.append("開始日は終了日より前である必要があります")
elif tool_name == "transfer":
if args.get("amount", 0) > args.get("balance", 0):
self.error_messages.append("送金額は残高を超えることはできません")
よくあるエラーと対処法
エラー1:Missing required parameter
原因: LLMが関数の必須パラメータを省略して呼び出す
解決策:
# パラメータ不足を検出した場合のフォールバック処理
def handle_missing_parameter(tool_name: str, missing_param: str, llm_response: dict) -> dict:
"""
不足パラメータを検出した際の処理
ユーザーに必要な情報を запросить
"""
return {
"status": "requires_input",
"message": f"'{missing_param}' の値が必要です。提供してください。",
"tool_name": tool_name,
"missing_parameter": missing_param,
"suggestion": f"例: 「{missing_param}: [値]」のように指定してください"
}
使用例
try:
validation_result = validation_layer.validate(tool_name, args, schema)
if not validation_result[0]:
# 最初の不足パラメータを通知
missing_param = extract_missing_param(validation_result[1])
return handle_missing_parameter(tool_name, missing_param, response)
except ValidationError as e:
print(f"Validation failed: {e}")
エラー2:Invalid parameter type
原因: LLMが型変換を誤って実行(例:数値を文字列として送信)
解決策:
# 型自動変換を用いた堅牢なパラメータ処理
import ast
def safe_parse_parameter(value: Any, expected_type: str) -> tuple[bool, Any]:
"""
パラメータを安全な型に変換 محاولة
Returns: (success, parsed_value)
"""
if expected_type == "integer":
try:
if isinstance(value, str):
# 文字列から整数への変換
return True, int(float(value))
return True, int(value)
except (ValueError, TypeError