diff --git a/conf/all_models.json b/conf/all_models.json index 629486a747..488daa2f7d 100644 --- a/conf/all_models.json +++ b/conf/all_models.json @@ -1411,7 +1411,10 @@ "max_tokens": 32000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "Baichuan-M2-Plus", @@ -1425,7 +1428,10 @@ "max_tokens": 32000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "Baichuan-Text-Embedding", @@ -1445,7 +1451,10 @@ "max_tokens": 32000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "Baichuan2-Turbo-192k", @@ -1459,35 +1468,50 @@ "max_tokens": 32000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "Baichuan3-Turbo-128k", "max_tokens": 128000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "Baichuan4", "max_tokens": 32000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "Baichuan4-Air", "max_tokens": 32000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "Baichuan4-Turbo", "max_tokens": 32000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "baidu/ernie-5.0-thinking-exp", @@ -3221,6 +3245,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -3299,7 +3326,10 @@ "alias": [ "claude-opus-4.6" ], - "max_tokens": 200000 + "max_tokens": 200000, + "tools": { + "support": true + } }, { "name": "claude-opus-4-7", @@ -3312,7 +3342,10 @@ ], "alias": [ "claude-opus-4.7" - ] + ], + "tools": { + "support": true + } }, { "name": "claude-opus-4-7-thinking", @@ -3380,7 +3413,10 @@ "model_types": [ "chat" ], - "alias": [] + "alias": [], + "tools": { + "support": true + } }, { "name": "claude-sonnet-4-5-20250929-thinking", @@ -3408,6 +3444,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -4091,6 +4130,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -4118,7 +4160,10 @@ "default_value": true, "clear_thinking": true }, - "max_completion_tokens": 8000 + "max_completion_tokens": 8000, + "tools": { + "support": true + } }, { "name": "deepseek/deepseek-v3/community", @@ -4129,7 +4174,10 @@ "alias": [ "community" ], - "max_completion_tokens": 8000 + "max_completion_tokens": 8000, + "tools": { + "support": true + } }, { "name": "deepseek-ai/deepseek-v4-flash", @@ -4148,6 +4196,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -4177,6 +4228,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -4944,7 +4998,10 @@ "image2text", "vision" ], - "max_completion_tokens": 8192 + "max_completion_tokens": 8192, + "tools": { + "support": true + } }, { "name": "deepseek-ocr-2", @@ -4973,6 +5030,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -4989,6 +5049,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -5137,7 +5200,10 @@ "model_types": [ "chat" ], - "max_completion_tokens": 16384 + "max_completion_tokens": 16384, + "tools": { + "support": true + } }, { "name": "deepseek-v3-1-think-250821", @@ -5147,6 +5213,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -5185,6 +5254,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -5207,6 +5279,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -5234,6 +5309,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -5249,6 +5327,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -5898,6 +5979,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -6593,14 +6677,20 @@ "name": "glm-4", "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "glm-4-0520", "max_tokens": 128000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "glm-4-5-air-20250728", @@ -6631,7 +6721,10 @@ "max_tokens": 128000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "glm-4-air-250414", @@ -6645,7 +6738,10 @@ "max_tokens": 8000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "glm-4-flash", @@ -6672,14 +6768,20 @@ "max_tokens": 1000000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "glm-4-plus", "max_tokens": 128000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "glm-4.1v-thinking-flash", @@ -6708,7 +6810,10 @@ "max_tokens": 128000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "glm-4.5-flash", @@ -6725,7 +6830,10 @@ "max_tokens": 128000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "glm-4.7-coding-preview", @@ -6786,6 +6894,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -6801,6 +6912,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -7631,7 +7745,10 @@ "alias": [], "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "google/gemma-2-2b-it-GGUF", @@ -7853,7 +7970,10 @@ "chat", "image2text", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "google/gemma-3-27b-it-qat-q4_0-gguf", @@ -9921,7 +10041,10 @@ "model_types": [ "chat" ], - "alias": [] + "alias": [], + "tools": { + "support": true + } }, { "name": "gpt-3.5-turbo-0125", @@ -9978,7 +10101,10 @@ ], "alias": [ "gpt-4-all" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-4-0125-preview", @@ -10009,7 +10135,10 @@ "max_tokens": 32000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-4-32k-0613", @@ -10085,7 +10214,10 @@ "model_types": [ "chat" ], - "alias": [] + "alias": [], + "tools": { + "support": true + } }, { "name": "gpt-4.1-2025-04-14", @@ -10101,7 +10233,10 @@ "model_types": [ "chat" ], - "alias": [] + "alias": [], + "tools": { + "support": true + } }, { "name": "gpt-4.1-mini-2025-04-14", @@ -10117,7 +10252,10 @@ "model_types": [ "chat" ], - "alias": [] + "alias": [], + "tools": { + "support": true + } }, { "name": "gpt-4.1-nano-2025-04-14", @@ -10137,7 +10275,10 @@ ], "alias": [ "gpt-4o-all" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-4o-2024-08-06", @@ -10206,7 +10347,10 @@ "model_types": [ "chat" ], - "alias": [] + "alias": [], + "tools": { + "support": true + } }, { "name": "gpt-4o-mini-2024-07-18", @@ -10361,7 +10505,10 @@ "model_types": [ "chat" ], - "alias": [] + "alias": [], + "tools": { + "support": true + } }, { "name": "gpt-5-codex-2025-09-15", @@ -10510,7 +10657,10 @@ "model_types": [ "chat" ], - "alias": [] + "alias": [], + "tools": { + "support": true + } }, { "name": "gpt-5.1-codex-2025-11-13", @@ -10608,6 +10758,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -10697,7 +10850,10 @@ "default_value": true, "clear_thinking": true }, - "alias": [] + "alias": [], + "tools": { + "support": true + } }, { "name": "gpt-5.4-nano-2026-03-17", @@ -11063,7 +11219,10 @@ "max_completion_tokens": 8192, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "groq/compound-mini", @@ -11074,7 +11233,10 @@ "max_completion_tokens": 8192, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "Gryphe/MythoMax-L2-13b-turbo", @@ -13799,7 +13961,10 @@ "clear_thinking": true }, "max_tokens": 262144, - "max_completion_tokens": 32768 + "max_completion_tokens": 32768, + "tools": { + "support": true + } }, { "name": "kimi-k2-thinking", @@ -13810,6 +13975,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -13847,6 +14015,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -14447,7 +14618,10 @@ "max_completion_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "llama-3.3-70b-versatile", @@ -14455,7 +14629,10 @@ "max_completion_tokens": 32768, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "llama-4-maverick", @@ -15088,7 +15265,10 @@ "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "meta-llama/Llama-3.2-11B-Vision", @@ -16367,6 +16547,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -16389,6 +16572,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -16459,6 +16645,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -16590,7 +16779,10 @@ "MiniMaxAI/MiniMax-M3", "MiniMax M3" ], - "max_completion_tokens": 131072 + "max_completion_tokens": 131072, + "tools": { + "support": true + } }, { "name": "MiniMax-Voice-Clone", @@ -17350,7 +17542,10 @@ "chat" ], "max_tokens": 131072, - "max_completion_tokens": 16000 + "max_completion_tokens": 16000, + "tools": { + "support": true + } }, { "name": "mistralai/Mistral-Nemo-Instruct-2407", @@ -17643,7 +17838,10 @@ "max_tokens": 128000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "moonshot-v1-128k-vision-preview", @@ -17652,14 +17850,20 @@ "chat", "image2text", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "moonshot-v1-32k", "max_tokens": 32000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "moonshot-v1-32k-vision-preview", @@ -17668,14 +17872,20 @@ "chat", "image2text", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "moonshot-v1-8k", "max_tokens": 8000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "moonshot-v1-8k-vision-preview", @@ -17684,7 +17894,10 @@ "chat", "image2text", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "moonshotai/Kimi-Audio-7B", @@ -17788,7 +18001,10 @@ "default_value": true, "clear_thinking": true }, - "max_completion_tokens": 262144 + "max_completion_tokens": 262144, + "tools": { + "support": true + } }, { "name": "moonshotai/Kimi-K2.5", @@ -18321,6 +18537,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -20662,7 +20881,10 @@ ], "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "nvidia/nemotron-3-super-120b-a12b:free", @@ -22995,7 +23217,10 @@ "default_value": true, "clear_thinking": true }, - "max_completion_tokens": 65536 + "max_completion_tokens": 65536, + "tools": { + "support": true + } }, { "name": "openai/gpt-oss-120b-Turbo", @@ -23029,7 +23254,10 @@ "default_value": true, "clear_thinking": true }, - "max_completion_tokens": 65536 + "max_completion_tokens": 65536, + "tools": { + "support": true + } }, { "name": "openai/gpt-oss-safeguard-120b", @@ -23970,7 +24198,10 @@ ], "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "orcarouter/fusion", @@ -24216,7 +24447,10 @@ "chat", "image2text", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "Pixverse/Pixverse-6-I2V", @@ -24873,7 +25107,10 @@ "default_value": true, "clear_thinking": true }, - "max_completion_tokens": 65536 + "max_completion_tokens": 65536, + "tools": { + "support": true + } }, { "name": "qwen/qwen3.7-max-preview", @@ -25049,7 +25286,10 @@ "default_value": true, "clear_thinking": true }, - "max_completion_tokens": 65536 + "max_completion_tokens": 65536, + "tools": { + "support": true + } }, { "name": "qwen/qwen3.5-plus-20260420", @@ -25161,7 +25401,10 @@ "default_value": true, "clear_thinking": true }, - "max_completion_tokens": 65536 + "max_completion_tokens": 65536, + "tools": { + "support": true + } }, { "name": "qwen/qwen3.5-27b", @@ -25498,6 +25741,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -26057,7 +26303,10 @@ "chat" ], "max_tokens": 32768, - "max_completion_tokens": 40960 + "max_completion_tokens": 40960, + "tools": { + "support": true + } }, { "name": "qwen/qwen3-8b", @@ -26068,7 +26317,10 @@ "model_types": [ "chat" ], - "max_tokens": 32768 + "max_tokens": 32768, + "tools": { + "support": true + } }, { "name": "qwen/qwen3-8b-base", @@ -26382,6 +26634,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -26536,6 +26791,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -27056,7 +27314,10 @@ "chat" ], "max_tokens": 262144, - "max_completion_tokens": 65536 + "max_completion_tokens": 65536, + "tools": { + "support": true + } }, { "name": "qwen/qwen3-coder-480b-a35b-instruct-fp8", @@ -28519,7 +28780,10 @@ "chat" ], "max_tokens": 131072, - "max_completion_tokens": 32000 + "max_completion_tokens": 32000, + "tools": { + "support": true + } }, { "name": "qwen/qwen2.5-32b-instruct-awq", @@ -32999,7 +33263,10 @@ ], "alias": [ "solar-mini-250422" - ] + ], + "tools": { + "support": true + } }, { "name": "solar-pro2", @@ -33008,7 +33275,10 @@ ], "alias": [ "solar-pro2-251215" - ] + ], + "tools": { + "support": true + } }, { "name": "solar-pro3", @@ -33017,7 +33287,10 @@ ], "alias": [ "solar-pro3-260323" - ] + ], + "tools": { + "support": true + } }, { "name": "sonar", @@ -33762,21 +34035,30 @@ "chat", "image2text", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "step-1v-32k", "max_tokens": 32000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "step-1v-8k", "max_tokens": 8000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "step-2-16k", @@ -33786,7 +34068,10 @@ "max_tokens": 16000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "step-2-16k-exp", diff --git a/conf/models/302ai.json b/conf/models/302ai.json index 8edb8d10c4..d499d34443 100644 --- a/conf/models/302ai.json +++ b/conf/models/302ai.json @@ -25,6 +25,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -33,7 +36,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-5.4", @@ -41,7 +47,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-5.4-mini", @@ -49,7 +58,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-5.4-nano", @@ -57,7 +69,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-5.2-pro", @@ -65,7 +80,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-5.2", @@ -73,7 +91,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-5.1", @@ -81,7 +102,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-5.1-chat-latest", @@ -89,7 +113,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-5", @@ -97,7 +124,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-5-mini", @@ -105,7 +135,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-5-nano", @@ -113,7 +146,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-5-chat-latest", @@ -121,7 +157,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-4.1", @@ -129,7 +168,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-4.1-mini", @@ -137,7 +179,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-4.1-nano", @@ -145,14 +190,20 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-4.5-preview", "max_tokens": 128000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-4o-mini", @@ -160,7 +211,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-4o", @@ -168,21 +222,30 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-3.5-turbo", "max_tokens": 4096, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-3.5-turbo-16k-0613", "max_tokens": 16385, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "whisper-v3-turbo", @@ -219,4 +282,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/conf/models/astraflow.json b/conf/models/astraflow.json index 27119a4fde..6bc34296b7 100644 --- a/conf/models/astraflow.json +++ b/conf/models/astraflow.json @@ -38,126 +38,180 @@ "max_tokens": 200000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "claude-opus-4-6", "max_tokens": 200000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "claude-sonnet-4-5-20250929", "max_tokens": 200000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "claude-haiku-4-5-20251001", "max_tokens": 200000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-5.4", "max_tokens": 400000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-5.4-mini", "max_tokens": 400000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-5.4-nano", "max_tokens": 400000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-4o-mini", "max_tokens": 128000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "Qwen/Qwen3-Max", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "Qwen/Qwen3-Coder", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "Qwen/Qwen3-32B", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "Qwen/Qwen3-VL-235B-A22B-Instruct", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "kimi-k2.6", "max_tokens": 200000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "glm-5.1", "max_tokens": 128000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "MiniMax-M2.7", "max_tokens": 1000000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "MiniMax-M2", "max_tokens": 1000000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "gemini-2.5-pro", "max_tokens": 1000000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "gemini-2.5-flash", "max_tokens": 1000000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } } ] -} \ No newline at end of file +} diff --git a/conf/models/avian.json b/conf/models/avian.json index b926350b1e..d12fcc7998 100644 --- a/conf/models/avian.json +++ b/conf/models/avian.json @@ -14,42 +14,60 @@ "max_tokens": 164000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "deepseek/deepseek-v4-flash", "max_tokens": 164000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "deepseek/deepseek-v3.2", "max_tokens": 164000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "moonshotai/kimi-k2.5", "max_tokens": 131000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "z-ai/glm-5", "max_tokens": 131000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "minimax/minimax-m2.5", "max_tokens": 1000000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } } ] } diff --git a/conf/models/baichuan.json b/conf/models/baichuan.json index 9a41310afa..80c024297c 100644 --- a/conf/models/baichuan.json +++ b/conf/models/baichuan.json @@ -14,70 +14,100 @@ "max_tokens": 32768, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "Baichuan4-Air", "max_tokens": 32768, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "Baichuan4-Turbo", "max_tokens": 32768, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "Baichuan-M3", "max_tokens": 32768, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "Baichuan-M3-plus", "max_tokens": 32768, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "Baichuan-M2-plus", "max_tokens": 32768, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "Baichuan-M2", "max_tokens": 32768, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "Baichuan3-Turbo", "max_tokens": 32768, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "Baichuan3-Turbo-128k", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "Baichuan2-Turbo", "max_tokens": 32768, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "Baichuan-Text-Embedding", diff --git a/conf/models/baidu.json b/conf/models/baidu.json index 21985aa3cd..b1e44cefa5 100644 --- a/conf/models/baidu.json +++ b/conf/models/baidu.json @@ -17,7 +17,10 @@ "max_tokens": 98304, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "deepseek-v4-flash", @@ -28,6 +31,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -39,6 +45,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -46,14 +55,20 @@ "max_tokens": 30720, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "qwen3-4b", "max_tokens": 30720, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "ernie-5.0", diff --git a/conf/models/cometapi.json b/conf/models/cometapi.json index 06cbbe1daf..1e5d359ab7 100644 --- a/conf/models/cometapi.json +++ b/conf/models/cometapi.json @@ -23,6 +23,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -35,6 +38,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -47,6 +53,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -55,7 +64,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "claude-sonnet-4-6", @@ -63,7 +75,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gemini-3-pro-preview", @@ -71,21 +86,30 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "deepseek-v3.2", "max_tokens": 128000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "qwen3-235b-a22b", "max_tokens": 128000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "text-embedding-3-small", diff --git a/conf/models/deepinfra.json b/conf/models/deepinfra.json index 67d49886a0..d892088efb 100644 --- a/conf/models/deepinfra.json +++ b/conf/models/deepinfra.json @@ -23,6 +23,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -47,4 +50,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/conf/models/deepseek.json b/conf/models/deepseek.json index 6870b478b6..f283973867 100644 --- a/conf/models/deepseek.json +++ b/conf/models/deepseek.json @@ -20,6 +20,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -31,7 +34,10 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } } ] -} \ No newline at end of file +} diff --git a/conf/models/futurmix.json b/conf/models/futurmix.json index 0ce5dc3bef..8f085b56a3 100644 --- a/conf/models/futurmix.json +++ b/conf/models/futurmix.json @@ -14,7 +14,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-5.4", @@ -22,7 +25,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-5.4-mini", @@ -30,7 +36,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-5.4-nano", @@ -38,7 +47,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "claude-opus-4-7", @@ -46,7 +58,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "claude-opus-4-6", @@ -54,7 +69,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "claude-sonnet-4-6", @@ -62,7 +80,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "claude-haiku-4-5-20251001", @@ -70,7 +91,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gemini-3.1-pro-preview", @@ -78,7 +102,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gemini-2.5-pro", @@ -86,7 +113,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gemini-2.5-flash", @@ -94,7 +124,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gemini-2.5-flash-lite", @@ -102,7 +135,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } } ] } diff --git a/conf/models/gitee.json b/conf/models/gitee.json index 6ab71759f1..bdd97d6a4c 100644 --- a/conf/models/gitee.json +++ b/conf/models/gitee.json @@ -23,21 +23,30 @@ "max_tokens": 32768, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "qwen3-0.6b", "max_tokens": 32768, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "glm-4.7-flash", "max_tokens": 204800, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "BAAI/bge-reranker-v2-m3", diff --git a/conf/models/google.json b/conf/models/google.json index d821be59f1..f8568e0606 100644 --- a/conf/models/google.json +++ b/conf/models/google.json @@ -18,6 +18,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { diff --git a/conf/models/groq.json b/conf/models/groq.json index 4ec32c6d2c..6f97725348 100644 --- a/conf/models/groq.json +++ b/conf/models/groq.json @@ -16,63 +16,90 @@ "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "llama-3.3-70b-versatile", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "openai/gpt-oss-120b", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "openai/gpt-oss-20b", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "groq/compound", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "groq/compound-mini", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "openai/gpt-oss-20b", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "meta-llama/llama-4-scout-17b-16e-instruct", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "qwen/qwen3-32b", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "canopylabs/orpheus-v1-english", diff --git a/conf/models/hunyuan.json b/conf/models/hunyuan.json index db4f3f61fb..8004e9a748 100644 --- a/conf/models/hunyuan.json +++ b/conf/models/hunyuan.json @@ -16,177 +16,345 @@ { "name": "hy3", "max_tokens": 131072, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "hy3-preview", "max_tokens": 131072, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "kimi-k3", "max_tokens": 131072, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "kimi-k2.7-code-highspeed", "max_tokens": 131072, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "kimi-k2.7-code", "max_tokens": 131072, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "glm-5.2", "max_tokens": 131072, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "minimax-m3", "max_tokens": 131072, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "deepseek-v4-flash-202605", "max_tokens": 131072, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "deepseek-v4-pro-202606", "max_tokens": 131072, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "deepseek-v4-pro", "max_tokens": 131072, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "deepseek-v4-flash", "max_tokens": 131072, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "hy-mt2-pro", "max_tokens": 8192, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "hy-mt2-plus", "max_tokens": 8192, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "hy-mt2-lite", "max_tokens": 8192, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "hy-role", "max_tokens": 32768, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "hunyuan-role-latest", "max_tokens": 32768, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "qwen3.5-flash", "max_tokens": 131072, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "qwen3.5-plus", "max_tokens": 131072, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "glm-5.1", "max_tokens": 131072, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "glm-5", "max_tokens": 131072, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "glm-5-turbo", "max_tokens": 131072, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "kimi-k2.6", "max_tokens": 131072, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "kimi-k2.5", "max_tokens": 131072, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "minimax-m2.7", "max_tokens": 131072, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "minimax-m2.5", "max_tokens": 131072, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "deepseek-v3.2", "max_tokens": 131072, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "glm-5v-turbo", "max_tokens": 131072, - "model_types": ["chat", "vision"] + "model_types": [ + "chat", + "vision" + ], + "tools": { + "support": true + } }, { "name": "hy-vision-2.0-instruct", "max_tokens": 32768, - "model_types": ["chat", "vision"] + "model_types": [ + "chat", + "vision" + ], + "tools": { + "support": true + } }, { "name": "youtu-vita", "max_tokens": 32768, - "model_types": ["chat", "vision"] + "model_types": [ + "chat", + "vision" + ], + "tools": { + "support": true + } }, { "name": "hunyuan-t1-vision-20250916", "max_tokens": 32768, - "model_types": ["chat", "vision"] + "model_types": [ + "chat", + "vision" + ], + "tools": { + "support": true + } }, { "name": "hunyuan-turbos-vision-video-20250728", "max_tokens": 32768, - "model_types": ["chat", "vision"] + "model_types": [ + "chat", + "vision" + ], + "tools": { + "support": true + } }, { "name": "kinfra-text-embedding-0.6b", "max_tokens": 8192, - "model_types": ["embedding"] + "model_types": [ + "embedding" + ] }, { "name": "kinfra-text-embedding-4b", "max_tokens": 8192, - "model_types": ["embedding"] + "model_types": [ + "embedding" + ] }, { "name": "kinfra-vl-embedding-2b", "max_tokens": 8192, - "model_types": ["embedding"] + "model_types": [ + "embedding" + ] }, { "name": "kinfra-vl-embedding-8b", "max_tokens": 8192, - "model_types": ["embedding"] + "model_types": [ + "embedding" + ] } ] } diff --git a/conf/models/jiekouai.json b/conf/models/jiekouai.json index c5f46cae10..1f831a56c2 100644 --- a/conf/models/jiekouai.json +++ b/conf/models/jiekouai.json @@ -20,6 +20,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -31,6 +34,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -42,6 +48,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -53,6 +62,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -64,6 +76,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -75,6 +90,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -86,6 +104,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { diff --git a/conf/models/jina.json b/conf/models/jina.json index 97069a4b9a..87c64a81e0 100644 --- a/conf/models/jina.json +++ b/conf/models/jina.json @@ -17,7 +17,10 @@ "max_tokens": 32768, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "jina-reranker-v3", diff --git a/conf/models/longcat.json b/conf/models/longcat.json index 9588a9a544..b0f41d9ce0 100644 --- a/conf/models/longcat.json +++ b/conf/models/longcat.json @@ -14,35 +14,50 @@ "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "LongCat-Flash-Lite", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "LongCat-Flash-Thinking-2601", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "LongCat-Flash-Omni-2603", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "LongCat-2.0-Preview", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } } ] } diff --git a/conf/models/mistral.json b/conf/models/mistral.json index 1454922e80..aac4c14f82 100644 --- a/conf/models/mistral.json +++ b/conf/models/mistral.json @@ -16,35 +16,50 @@ "max_tokens": 128000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "mistral-medium-latest", "max_tokens": 128000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "mistral-small-latest", "max_tokens": 128000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "ministral-8b-latest", "max_tokens": 128000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "ministral-3b-latest", "max_tokens": 128000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "pixtral-large-latest", @@ -52,56 +67,80 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "codestral-latest", "max_tokens": 256000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "open-mistral-nemo", "max_tokens": 128000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "open-mistral-7b", "max_tokens": 32000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "open-mixtral-8x7b", "max_tokens": 32000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "open-mixtral-8x22b", "max_tokens": 64000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "magistral-medium-latest", "max_tokens": 40000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "magistral-small-latest", "max_tokens": 40000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "mistral-embed", diff --git a/conf/models/moonshot.json b/conf/models/moonshot.json index be2359dd48..8598401463 100644 --- a/conf/models/moonshot.json +++ b/conf/models/moonshot.json @@ -21,6 +21,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -33,6 +36,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -41,21 +47,30 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "moonshot-v1-32k", "max_tokens": 32000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "moonshot-v1-128k", "max_tokens": 128000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "moonshot-v1-8k-vision-preview", @@ -63,7 +78,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "moonshot-v1-32k-vision-preview", @@ -71,7 +89,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "moonshot-v1-128k-vision-preview", @@ -79,7 +100,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } } ] -} \ No newline at end of file +} diff --git a/conf/models/n1n.json b/conf/models/n1n.json index a91da682ea..4ba945afd3 100644 --- a/conf/models/n1n.json +++ b/conf/models/n1n.json @@ -17,7 +17,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-4o", @@ -25,7 +28,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-5.2", @@ -33,7 +39,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "claude-sonnet-4-6", @@ -41,42 +50,60 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "deepseek-v3-0324", "max_tokens": 128000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "deepseek-v3-1-250821", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "deepseek-v3-1-think-250821", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "kimi-k2-250905", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "qwen3-coder-plus", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "text-embedding-3-small", diff --git a/conf/models/novita.json b/conf/models/novita.json index 2f2612d05d..80f9093ae2 100644 --- a/conf/models/novita.json +++ b/conf/models/novita.json @@ -17,49 +17,70 @@ "max_tokens": 65536, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "meta-llama/llama-3.3-70b-instruct", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "qwen/qwen3-30b-a3b-fp8", "max_tokens": 32768, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "qwen/qwen3-235b-a22b-fp8", "max_tokens": 32768, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "moonshotai/kimi-k2-instruct", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "google/gemma-3-27b-it", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "mistralai/mistral-nemo", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "baai/bge-m3", diff --git a/conf/models/nvidia.json b/conf/models/nvidia.json index 608db40a35..9ef55bdbb7 100644 --- a/conf/models/nvidia.json +++ b/conf/models/nvidia.json @@ -16,28 +16,40 @@ "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "bytedance/seed-oss-36b-instruct", "max_tokens": 32768, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "deepseek-ai/deepseek-v4-flash", "max_tokens": 1048576, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "deepseek-ai/deepseek-v4-pro", "max_tokens": 1048576, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "nvidia/nv-embed-v1", @@ -51,21 +63,30 @@ "max_tokens": 8192, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "google/gemma-2-2b-it", "max_tokens": 8192, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "google/gemma-4-31b-it", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "meta/llama-3.2-90b-vision-instruct", @@ -73,42 +94,60 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "meta/llama-4-maverick-17b-128e-instruct", "max_tokens": 1048576, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "minimaxai/minimax-m2.5", "max_tokens": 204800, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "minimaxai/minimax-m2.7", "max_tokens": 204800, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "mistralai/mistral-7b-instruct-v0.3", "max_tokens": 32768, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "mistralai/mistral-large-3-675b-instruct-2512", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "mistralai/mistral-medium-3.5-128b", @@ -116,14 +155,20 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "mistralai/mistral-nemotron", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "moonshotai/kimi-k2.6", @@ -131,14 +176,20 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "moonshotai/kimi-k2-instruct", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "moonshotai/kimi-k2-thinking", @@ -149,6 +200,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -156,35 +210,50 @@ "max_tokens": 4096, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "nvidia/llama-3.1-nemoguard-8b-content-safety", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "nvidia/llama-3.1-nemoguard-8b-topic-control", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "nvidia/llama-3.1-nemotron-nano-8b-v1", "max_tokens": 8192, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "nvidia/llama-3.1-nemotron-safety-guard-8b-v3", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "nvidia/llama-3.1-nemotron-ultra-253b-v1", @@ -195,6 +264,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -209,7 +281,10 @@ "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "nvidia/llama-3.3-nemotron-super-49b-v1.5", @@ -220,6 +295,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -227,7 +305,10 @@ "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning", @@ -239,6 +320,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -246,21 +330,30 @@ "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "nvidia/nemotron-content-safety-reasoning-4b", "max_tokens": 8192, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "nvidia/nemotron-mini-4b-instruct", "max_tokens": 4096, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "nvidia/nv-embed-v1", @@ -302,28 +395,40 @@ "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "nvidia/riva-translate-4b-instruct-v1.1", "max_tokens": 4096, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "openai/gpt-oss-120b", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "qwen/qwen3.5-122b-a10b", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "qwen/qwen3-coder-480b-a35b-instruct", @@ -334,6 +439,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -345,6 +453,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -356,6 +467,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -367,6 +481,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } } ] diff --git a/conf/models/openai.json b/conf/models/openai.json index 9e9f5c3510..1ba96ed74b 100644 --- a/conf/models/openai.json +++ b/conf/models/openai.json @@ -19,7 +19,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-5.4", @@ -27,7 +30,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-5.4-mini", @@ -35,7 +41,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-5.4-nano", @@ -43,7 +52,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-5.2-pro", @@ -51,7 +63,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-5.2", @@ -59,7 +74,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-5.1", @@ -67,7 +85,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-5.1-chat-latest", @@ -75,7 +96,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-5", @@ -83,7 +107,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-5-mini", @@ -91,7 +118,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-5-nano", @@ -99,7 +129,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-5-chat-latest", @@ -107,7 +140,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-4.1", @@ -115,7 +151,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-4.1-mini", @@ -123,7 +162,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-4.1-nano", @@ -131,14 +173,20 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-4.5-preview", "max_tokens": 128000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-4o-mini", @@ -146,7 +194,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-4o", @@ -154,21 +205,30 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-3.5-turbo", "max_tokens": 4096, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-3.5-turbo-16k-0613", "max_tokens": 16385, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "text-embedding-ada-002", @@ -203,21 +263,30 @@ "max_tokens": 8191, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-4-turbo", "max_tokens": 8191, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-4-32k", "max_tokens": 32768, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "tts-1", diff --git a/conf/models/openrouter.json b/conf/models/openrouter.json index 1833a0921e..f2e3dc5d77 100644 --- a/conf/models/openrouter.json +++ b/conf/models/openrouter.json @@ -24,6 +24,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -35,6 +38,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -46,6 +52,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { diff --git a/conf/models/orcarouter.json b/conf/models/orcarouter.json index 3fbce77445..1ee07900e1 100644 --- a/conf/models/orcarouter.json +++ b/conf/models/orcarouter.json @@ -15,7 +15,10 @@ "max_tokens": 128000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "openai/tts-1", @@ -24,4 +27,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/conf/models/ppio.json b/conf/models/ppio.json index 7262497987..31a03913f9 100644 --- a/conf/models/ppio.json +++ b/conf/models/ppio.json @@ -15,147 +15,210 @@ "max_tokens": 1048576, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "deepseek/deepseek-v4-pro", "max_tokens": 1048576, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "deepseek/deepseek-r1/community", "max_tokens": 64000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "deepseek/deepseek-v3/community", "max_tokens": 64000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "deepseek/deepseek-r1", "max_tokens": 64000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "deepseek/deepseek-v3", "max_tokens": 64000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "deepseek/deepseek-r1-distill-llama-70b", "max_tokens": 32000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "deepseek/deepseek-r1-distill-qwen-32b", "max_tokens": 64000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "deepseek/deepseek-r1-distill-qwen-14b", "max_tokens": 64000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "deepseek/deepseek-r1-distill-llama-8b", "max_tokens": 32000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "qwen/qwen-2.5-72b-instruct", "max_tokens": 32768, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "qwen/qwen-2-vl-72b-instruct", "max_tokens": 32768, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "meta-llama/llama-3.2-3b-instruct", "max_tokens": 32768, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "qwen/qwen2.5-32b-instruct", "max_tokens": 32000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "baichuan/baichuan2-13b-chat", "max_tokens": 14336, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "meta-llama/llama-3.1-70b-instruct", "max_tokens": 32768, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "meta-llama/llama-3.1-8b-instruct", "max_tokens": 32768, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "01-ai/yi-1.5-34b-chat", "max_tokens": 16384, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "01-ai/yi-1.5-9b-chat", "max_tokens": 16384, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "thudm/glm-4-9b-chat", "max_tokens": 32768, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "qwen/qwen-2-7b-instruct", "max_tokens": 32768, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } } ] } diff --git a/conf/models/qiniu.json b/conf/models/qiniu.json index 0e0b18eac5..0a72686fd6 100644 --- a/conf/models/qiniu.json +++ b/conf/models/qiniu.json @@ -12,7 +12,9 @@ { "name": "deepseek/deepseek-v4-flash", "max_tokens": 1048576, - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true }, @@ -24,7 +26,9 @@ { "name": "deepseek/deepseek-v4-pro", "max_tokens": 1048576, - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true }, @@ -35,7 +39,9 @@ }, { "name": "moonshotai/kimi-k2.6", - "model_types": ["vision"], + "model_types": [ + "vision" + ], "thinking": { "default_value": true, "clear_thinking": true @@ -43,11 +49,15 @@ }, { "name": "moonshotai/kimi-k2.5", - "model_types": ["vision"] + "model_types": [ + "vision" + ] }, { "name": "z-ai/glm-5.1", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true }, @@ -58,7 +68,9 @@ }, { "name": "z-ai/glm-5", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true }, @@ -69,7 +81,9 @@ }, { "name": "minimax/minimax-m2.7", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true }, @@ -80,7 +94,9 @@ }, { "name": "minimax/minimax-m2.5", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true }, @@ -91,7 +107,9 @@ }, { "name": "minimax/minimax-m2.5-highspeed", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true }, @@ -102,7 +120,9 @@ }, { "name": "minimax/minimax-m2.1", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true } @@ -110,7 +130,9 @@ { "name": "kimi-k2-thinking", "max_tokens": 262144, - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true }, @@ -121,14 +143,18 @@ }, { "name": "meituan/longcat-flash-lite", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true } }, { "name": "qwen3-max", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true } @@ -136,7 +162,9 @@ { "name": "z-ai/glm-4.6", "max_tokens": 204800, - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true }, @@ -147,14 +175,18 @@ }, { "name": "z-ai/glm-4.7", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true } }, { "name": "deepseek/deepseek-v3.2-251201", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true }, @@ -165,7 +197,9 @@ }, { "name": "deepseek/deepseek-v3.2-exp-thinking", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true }, @@ -176,14 +210,18 @@ }, { "name": "deepseek/deepseek-v3.1-terminus", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true } }, { "name": "deepseek/deepseek-v3.1-terminus-thinking", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true }, @@ -194,21 +232,27 @@ }, { "name": "deepseek-v3.1", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true } }, { "name": "deepseek-v3-0324", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true } }, { "name": "deepseek-r1-0528", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true }, @@ -219,7 +263,9 @@ }, { "name": "deepseek-r1", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true }, @@ -231,7 +277,9 @@ { "name": "doubao-seed-1.6-flash", "max_tokens": 262144, - "model_types": ["vision"], + "model_types": [ + "vision" + ], "thinking": { "default_value": true, "clear_thinking": true @@ -240,12 +288,16 @@ { "name": "doubao-1.5-pro-32k", "max_tokens": 131072, - "model_types": ["vision"] + "model_types": [ + "vision" + ] }, { "name": "doubao-seed-1.6", "max_tokens": 262144, - "model_types": ["vision"], + "model_types": [ + "vision" + ], "thinking": { "default_value": true, "clear_thinking": true @@ -253,7 +305,9 @@ }, { "name": "doubao-seed-2.0-pro", - "model_types": ["vision"], + "model_types": [ + "vision" + ], "thinking": { "default_value": true, "clear_thinking": true @@ -261,7 +315,9 @@ }, { "name": "doubao-seed-2.0-lite", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true }, @@ -273,7 +329,9 @@ { "name": "doubao-seed-2.0-mini", "max_tokens": 262144, - "model_types": ["vision"], + "model_types": [ + "vision" + ], "thinking": { "default_value": true, "clear_thinking": true @@ -281,7 +339,9 @@ }, { "name": "doubao-seed-2.0-code", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true }, @@ -292,7 +352,9 @@ }, { "name": "qwen3-next-80b-a3b-thinking", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true }, @@ -303,7 +365,9 @@ }, { "name": "qwen3-235b-a22b-thinking-2507", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true }, @@ -314,7 +378,9 @@ }, { "name": "qwen3-max-2026-01-23", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true }, @@ -325,32 +391,42 @@ }, { "name": "qwen3-next-80b-a3b-instruct", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true } }, { "name": "qwen3-max-preview", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true } }, { "name": "qwen-2.5-vl-72b-instruct", - "model_types": ["vision"] + "model_types": [ + "vision" + ] }, { "name": "qwen3-coder-480b-a35b-instruct", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true } }, { "name": "qwen-turbo", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true }, @@ -361,14 +437,18 @@ }, { "name": "qwen3-235b-a22b-instruct-2507", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true } }, { "name": "qwen3-32b", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true }, @@ -379,7 +459,9 @@ }, { "name": "qwen3-30b-a3b", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true }, @@ -390,22 +472,30 @@ }, { "name": "qwen3-235b-a22b", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true } }, { "name": "qwen-2.5-vl-7b-instruct", - "model_types": ["vision"] + "model_types": [ + "vision" + ] }, { "name": "qwen-vl-max-2025-01-25", - "model_types": ["vision"] + "model_types": [ + "vision" + ] }, { "name": "qwen2.5-max-2025-01-25", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true } @@ -413,7 +503,9 @@ { "name": "minimax-m1", "max_tokens": 1048576, - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true }, @@ -424,7 +516,9 @@ }, { "name": "glm-4.5", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true }, @@ -435,18 +529,24 @@ }, { "name": "qwen3-vl-30b-a3b-instruct", - "model_types": ["vision"] + "model_types": [ + "vision" + ] }, { "name": "deepseek-v3", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true } }, { "name": "qwen3-30b-a3b-thinking-2507", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true }, @@ -457,7 +557,9 @@ }, { "name": "glm-4.5-air", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true }, @@ -468,7 +570,9 @@ }, { "name": "qwen3.5-397b-a17b", - "model_types": ["vision"], + "model_types": [ + "vision" + ], "thinking": { "default_value": true, "clear_thinking": true @@ -476,11 +580,15 @@ }, { "name": "qwen/qwen3.5-plus", - "model_types": ["vision"] + "model_types": [ + "vision" + ] }, { "name": "qwen/qwen3.6-plus", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true }, @@ -491,14 +599,18 @@ }, { "name": "deepseek/deepseek-v3.2-exp", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true } }, { "name": "qwen/qwen3.7-max", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true }, @@ -510,7 +622,9 @@ { "name": "qwen/qwen3.6-27b", "max_tokens": 262144, - "model_types": ["vision"], + "model_types": [ + "vision" + ], "thinking": { "default_value": true, "clear_thinking": true @@ -518,7 +632,9 @@ }, { "name": "tencent/hy3-preview", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true }, @@ -529,7 +645,9 @@ }, { "name": "qwen3.5-35b-a3b", - "model_types": ["vision"], + "model_types": [ + "vision" + ], "thinking": { "default_value": true, "clear_thinking": true @@ -537,7 +655,9 @@ }, { "name": "qwen3-vl-30b-a3b-thinking", - "model_types": ["vision"], + "model_types": [ + "vision" + ], "thinking": { "default_value": true, "clear_thinking": true @@ -545,7 +665,9 @@ }, { "name": "qwen3-30b-a3b-instruct-2507", - "model_types": ["chat"], + "model_types": [ + "chat" + ], "tools": { "support": true } diff --git a/conf/models/stepfun.json b/conf/models/stepfun.json index 06ef52d673..4e0abdcea5 100644 --- a/conf/models/stepfun.json +++ b/conf/models/stepfun.json @@ -15,56 +15,80 @@ "max_tokens": 32768, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "step-3.5-flash-paid", "max_tokens": 32768, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "step-2-16k", "max_tokens": 16384, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "step-1-256k", "max_tokens": 262144, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "step-1-128k", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "step-1-32k", "max_tokens": 32768, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "step-1-8k", "max_tokens": 8192, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "step-1-flash", "max_tokens": 8192, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "step-1v-32k", @@ -72,7 +96,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "step-1v-8k", @@ -80,7 +107,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "step-1o-vision-32k", @@ -88,7 +118,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "step-tts-2", diff --git a/conf/models/togetherai.json b/conf/models/togetherai.json index 2ec8983769..b43acf35fc 100644 --- a/conf/models/togetherai.json +++ b/conf/models/togetherai.json @@ -18,21 +18,30 @@ "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "meta-llama/Llama-3.3-70B-Instruct-Turbo", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "Qwen/Qwen3-Coder-480B-A35B-Instruct-FP8", "max_tokens": 262144, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "intfloat/multilingual-e5-large-instruct", @@ -76,4 +85,3 @@ } ] } - diff --git a/conf/models/tokenhub.json b/conf/models/tokenhub.json index ba64fb987f..a9b6d3895d 100644 --- a/conf/models/tokenhub.json +++ b/conf/models/tokenhub.json @@ -16,7 +16,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-4o", @@ -24,21 +27,30 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-4", "max_tokens": 8191, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "gpt-4-turbo", "max_tokens": 8191, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "claude-3-5-sonnet", @@ -46,7 +58,10 @@ "model_types": [ "chat", "vision" - ] + ], + "tools": { + "support": true + } }, { "name": "gemini-1.5-pro", @@ -57,6 +72,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { @@ -68,6 +86,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } } ] diff --git a/conf/models/tokenpony.json b/conf/models/tokenpony.json index b2d0e5ed7e..5c9e9e95c1 100644 --- a/conf/models/tokenpony.json +++ b/conf/models/tokenpony.json @@ -12,82 +12,162 @@ { "name": "qwen3-8b", "max_tokens": 128000, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "deepseek-v3-0324", "max_tokens": 128000, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "qwen3-32b", "max_tokens": 128000, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "kimi-k2-instruct-0905", "max_tokens": 256000, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "deepseek-r1-0528", "max_tokens": 164000, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "qwen3-coder-480b", "max_tokens": 1024000, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "hunyuan-a13b-instruct", "max_tokens": 256000, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "qwen3-next-80b-a3b-instruct", "max_tokens": 1024000, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "deepseek-v3.2-exp", "max_tokens": 128000, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "deepseek-v3.1-terminus", "max_tokens": 128000, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "qwen3-vl-235b-a22b-instruct", "max_tokens": 262000, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "qwen3-vl-30b-a3b-instruct", "max_tokens": 262000, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "deepseek-ocr", "max_tokens": 8000, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "qwen3-235b-a22b-instruct-2507", "max_tokens": 256000, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "glm-4.6", "max_tokens": 200000, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } }, { "name": "minimax-m2", "max_tokens": 200000, - "model_types": ["chat"] + "model_types": [ + "chat" + ], + "tools": { + "support": true + } } ] } diff --git a/conf/models/upstage.json b/conf/models/upstage.json index 045bcaf693..7fb1233d3a 100644 --- a/conf/models/upstage.json +++ b/conf/models/upstage.json @@ -15,28 +15,40 @@ "max_tokens": 65536, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "solar-pro2", "max_tokens": 65536, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "solar-pro", "max_tokens": 32768, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "solar-mini", "max_tokens": 32768, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "solar-embedding-1-large-query", diff --git a/conf/models/volcengine.json b/conf/models/volcengine.json index ab2c72e40d..34bab3ecf4 100644 --- a/conf/models/volcengine.json +++ b/conf/models/volcengine.json @@ -21,6 +21,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } }, { diff --git a/conf/models/xai.json b/conf/models/xai.json index f350cb5878..9cd24f5584 100644 --- a/conf/models/xai.json +++ b/conf/models/xai.json @@ -17,35 +17,50 @@ "max_tokens": 256000, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "grok-3", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "grok-3-fast", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "grok-3-mini", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "grok-3-mini-mini-fast", "max_tokens": 131072, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "grok-2-vision", diff --git a/conf/models/xiaomi.json b/conf/models/xiaomi.json index e3f9934f90..deb9a2bd1c 100644 --- a/conf/models/xiaomi.json +++ b/conf/models/xiaomi.json @@ -12,14 +12,20 @@ "max_tokens": 1048576, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "mimo-v2.5", "max_tokens": 1048576, "model_types": [ "chat" - ] + ], + "tools": { + "support": true + } }, { "name": "mimo-v2.5-asr", diff --git a/conf/models/xunfei.json b/conf/models/xunfei.json index 4473b74e3b..aa04ec5d4a 100644 --- a/conf/models/xunfei.json +++ b/conf/models/xunfei.json @@ -17,6 +17,9 @@ "thinking": { "default_value": true, "clear_thinking": true + }, + "tools": { + "support": true } } ] diff --git a/internal/entity/models/302ai.go b/internal/entity/models/302ai.go index 7e693c3925..fa2dfaa5e6 100644 --- a/internal/entity/models/302ai.go +++ b/internal/entity/models/302ai.go @@ -166,7 +166,8 @@ func (a *AI302Model) ChatWithMessages(ctx context.Context, modelName string, mes } content, ok := messageMap["content"].(string) - if !ok { + toolCalls := extractToolCalls(messageMap) + if !ok && len(toolCalls) == 0 { return nil, fmt.Errorf("invalid content format") } @@ -185,6 +186,7 @@ func (a *AI302Model) ChatWithMessages(ctx context.Context, modelName string, mes chatResponse := &ChatResponse{ Answer: &content, ReasonContent: &reasonContent, + ToolCalls: toolCalls, } return chatResponse, nil @@ -263,6 +265,7 @@ func (a *AI302Model) ChatStreamlyWithSender(ctx context.Context, modelName strin return fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body)) } + accumulatedToolCalls := make(map[int]map[string]any) if _, err = ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error { choices, ok := event["choices"].([]interface{}) if !ok || len(choices) == 0 { @@ -279,6 +282,8 @@ func (a *AI302Model) ChatStreamlyWithSender(ctx context.Context, modelName strin return nil } + accumulateToolCallDeltas(delta, accumulatedToolCalls) + reasoningContent, ok := delta["reasoning_content"].(string) if ok && reasoningContent != "" { if err = sender(nil, &reasoningContent); err != nil { @@ -298,6 +303,8 @@ func (a *AI302Model) ChatStreamlyWithSender(ctx context.Context, modelName strin return fmt.Errorf("failed to scan response body: %w", err) } + setSortedToolCallsResult(modelConfig, accumulatedToolCalls) + // Send [DONE] marker for OpenAI compatibility endOfStream := "[DONE]" return sender(&endOfStream, nil) diff --git a/internal/entity/models/astraflow.go b/internal/entity/models/astraflow.go index 94afc68427..7cceac6c10 100644 --- a/internal/entity/models/astraflow.go +++ b/internal/entity/models/astraflow.go @@ -139,7 +139,8 @@ func (a *AstraflowModel) ChatWithMessages(ctx context.Context, modelName string, } content, ok := messageMap["content"].(string) - if !ok { + toolCalls := extractToolCalls(messageMap) + if !ok && len(toolCalls) == 0 { return nil, fmt.Errorf("invalid content format") } @@ -151,6 +152,7 @@ func (a *AstraflowModel) ChatWithMessages(ctx context.Context, modelName string, return &ChatResponse{ Answer: &content, ReasonContent: &reasonContent, + ToolCalls: toolCalls, }, nil } @@ -205,6 +207,7 @@ func (a *AstraflowModel) ChatStreamlyWithSender(ctx context.Context, modelName s } sawTerminal := false + accumulatedToolCalls := make(map[int]map[string]any) done, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error { if apiErr, ok := event["error"]; ok { return fmt.Errorf("astraflow: upstream stream error: %v", apiErr) @@ -219,6 +222,7 @@ func (a *AstraflowModel) ChatStreamlyWithSender(ctx context.Context, modelName s return nil } if delta, ok := firstChoice["delta"].(map[string]interface{}); ok { + accumulateToolCallDeltas(delta, accumulatedToolCalls) if r, ok := delta["reasoning_content"].(string); ok && r != "" { rr := r if err := sender(nil, &rr); err != nil { @@ -240,6 +244,7 @@ func (a *AstraflowModel) ChatStreamlyWithSender(ctx context.Context, modelName s if err != nil { return fmt.Errorf("failed to scan response body: %w", err) } + setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls) if !done && !sawTerminal { return fmt.Errorf("astraflow: stream ended before [DONE] or finish_reason") } diff --git a/internal/entity/models/avian.go b/internal/entity/models/avian.go index f1823a06fb..edc1f9ff1e 100644 --- a/internal/entity/models/avian.go +++ b/internal/entity/models/avian.go @@ -198,31 +198,39 @@ func (a *AvianModel) ChatStreamlyWithSender(ctx context.Context, modelName strin } sawTerminal := false - done, err := ParseSSEStream[avianChatResponse](resp.Body, func(event avianChatResponse) error { - if event.Error != nil { - return fmt.Errorf("avian: upstream stream error: %v", event.Error) - } - if len(event.Choices) == 0 { + accumulatedToolCalls := make(map[int]map[string]any) + done, err := ParseSSEStream[map[string]any](resp.Body, func(event map[string]any) error { + choices, ok := event["choices"].([]interface{}) + if !ok || len(choices) == 0 { return nil } - choice := event.Choices[0] - if reasoning := choice.Delta.ReasoningContent; reasoning != "" { - if err := sender(nil, &reasoning); err != nil { - return err - } - } else if reasoning := choice.Delta.Reasoning; reasoning != "" { - if err := sender(nil, &reasoning); err != nil { + + firstChoice, ok := choices[0].(map[string]interface{}) + if !ok { + return nil + } + + delta, ok := firstChoice["delta"].(map[string]interface{}) + if !ok { + return nil + } + + accumulateToolCallDeltas(delta, accumulatedToolCalls) + + reasoningContent, ok := delta["reasoning_content"].(string) + if ok && reasoningContent != "" { + if err = sender(nil, &reasoningContent); err != nil { return err } } - if choice.Delta.Content != "" { - if err := sender(&choice.Delta.Content, nil); err != nil { + + content, ok := delta["content"].(string) + if ok && content != "" { + if err = sender(&content, nil); err != nil { return err } } - if choice.FinishReason != "" || event.FinishReason != "" { - sawTerminal = true - } + return nil }) if err != nil { @@ -232,6 +240,8 @@ func (a *AvianModel) ChatStreamlyWithSender(ctx context.Context, modelName strin return fmt.Errorf("avian: stream ended before [DONE] or finish_reason") } + setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls) + endOfStream := "[DONE]" return sender(&endOfStream, nil) } diff --git a/internal/entity/models/baichuan.go b/internal/entity/models/baichuan.go index 87083365f2..f51d79a6de 100644 --- a/internal/entity/models/baichuan.go +++ b/internal/entity/models/baichuan.go @@ -117,7 +117,8 @@ func (b *BaichuanModel) ChatWithMessages(ctx context.Context, modelName string, } content, ok := messageMap["content"].(string) - if !ok { + toolCalls := extractToolCalls(messageMap) + if !ok && len(toolCalls) == 0 { return nil, fmt.Errorf("no message in response") } @@ -126,6 +127,7 @@ func (b *BaichuanModel) ChatWithMessages(ctx context.Context, modelName string, chatResponse := &ChatResponse{ Answer: &content, ReasonContent: &emptyReason, + ToolCalls: toolCalls, } return chatResponse, nil @@ -177,6 +179,7 @@ func (b *BaichuanModel) ChatStreamlyWithSender(ctx context.Context, modelName st // SSE parsing: read line by line sawTerminal := false + accumulatedToolCalls := make(map[int]map[string]any) done, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error { common.Info(fmt.Sprintf("%v", event)) @@ -195,6 +198,8 @@ func (b *BaichuanModel) ChatStreamlyWithSender(ctx context.Context, modelName st return nil } + accumulateToolCallDeltas(delta, accumulatedToolCalls) + content, ok := delta["content"].(string) if ok && content != "" { if err := sender(&content, nil); err != nil { @@ -215,6 +220,8 @@ func (b *BaichuanModel) ChatStreamlyWithSender(ctx context.Context, modelName st return fmt.Errorf("baichuan: stream ended before [DONE] or finish_reason") } + setSortedToolCallsResult(modelConfig, accumulatedToolCalls) + // Send [DONE] marker for OpenAI compatibility endOfStream := "[DONE]" return sender(&endOfStream, nil) diff --git a/internal/entity/models/cohere.go b/internal/entity/models/cohere.go index de0a094fb0..82d4363def 100644 --- a/internal/entity/models/cohere.go +++ b/internal/entity/models/cohere.go @@ -224,6 +224,7 @@ func (c *CoHereModel) ChatStreamlyWithSender(ctx context.Context, modelName stri } sawTerminal := false + accumulatedToolCalls := make(map[int]map[string]any) done, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error { eventType, ok := event["type"].(string) if !ok { @@ -240,6 +241,9 @@ func (c *CoHereModel) ChatStreamlyWithSender(ctx context.Context, modelName stri if !ok { return nil } + + accumulateToolCallDeltas(delta, accumulatedToolCalls) + msg, ok := delta["message"].(map[string]interface{}) if !ok { return nil @@ -270,6 +274,8 @@ func (c *CoHereModel) ChatStreamlyWithSender(ctx context.Context, modelName stri return fmt.Errorf("Cohere: stream ended before [DONE] or finish_reason") } + setSortedToolCallsResult(modelConfig, accumulatedToolCalls) + endOfStream := "[DONE]" return sender(&endOfStream, nil) } diff --git a/internal/entity/models/cometapi.go b/internal/entity/models/cometapi.go index af395b8cbe..dbef044951 100644 --- a/internal/entity/models/cometapi.go +++ b/internal/entity/models/cometapi.go @@ -287,29 +287,43 @@ func (c *CometAPIModel) ChatStreamlyWithSender(ctx context.Context, modelName st } sawTerminal := false - done, err := ParseSSEStream[cometapiChatResponsePayload](resp.Body, func(event cometapiChatResponsePayload) error { - if len(event.Choices) == 0 { + accumulatedToolCalls := make(map[int]map[string]any) + done, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error { + choices, ok := event["choices"].([]interface{}) + if !ok || len(choices) == 0 { return nil } - choice := event.Choices[0] - reasoningContent := choice.Delta.ReasoningContent - content := choice.Delta.Content - if reasoningContent != "" { - if err := sender(nil, &reasoningContent); err != nil { + firstChoice, ok := choices[0].(map[string]interface{}) + if !ok { + return nil + } + + delta, ok := firstChoice["delta"].(map[string]interface{}) + if !ok { + return nil + } + + accumulateToolCallDeltas(delta, accumulatedToolCalls) + + reasoningContent, ok := delta["reasoning_content"].(string) + if ok && reasoningContent != "" { + if err = sender(nil, &reasoningContent); err != nil { return err } } - if content != "" { - if err := sender(&content, nil); err != nil { + content, ok := delta["content"].(string) + if ok && content != "" { + if err = sender(&content, nil); err != nil { return err } } - if choice.FinishReason != "" { + if finishReason, ok := firstChoice["finish_reason"].(string); ok && finishReason != "" { sawTerminal = true } + return nil }) if err != nil { @@ -319,6 +333,8 @@ func (c *CometAPIModel) ChatStreamlyWithSender(ctx context.Context, modelName st return fmt.Errorf("cometapi: stream ended before [DONE] or finish_reason") } + setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls) + endOfStream := "[DONE]" if err := sender(&endOfStream, nil); err != nil { return err diff --git a/internal/entity/models/deepinfra.go b/internal/entity/models/deepinfra.go index 67a39842b1..ea1f3f6315 100644 --- a/internal/entity/models/deepinfra.go +++ b/internal/entity/models/deepinfra.go @@ -137,7 +137,8 @@ func (d *DeepInfraModel) ChatWithMessages(ctx context.Context, modelName string, } content, ok := messageMap["content"].(string) - if !ok { + toolCalls := extractToolCalls(messageMap) + if !ok && len(toolCalls) == 0 { return nil, fmt.Errorf("invalid content format") } @@ -216,6 +217,7 @@ func (d *DeepInfraModel) ChatStreamlyWithSender(ctx context.Context, modelName s } sawTerminal := false + accumulatedToolCalls := make(map[int]map[string]any) done, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error { common.Info(fmt.Sprintf("%v", event)) @@ -234,6 +236,8 @@ func (d *DeepInfraModel) ChatStreamlyWithSender(ctx context.Context, modelName s return nil } + accumulateToolCallDeltas(delta, accumulatedToolCalls) + reasoningContent, ok := delta["reasoning_content"].(string) if ok && reasoningContent != "" { if err := sender(nil, &reasoningContent); err != nil { @@ -257,6 +261,7 @@ func (d *DeepInfraModel) ChatStreamlyWithSender(ctx context.Context, modelName s if err != nil { return fmt.Errorf("failed to scan response body: %w", err) } + setSortedToolCallsResult(modelConfig, accumulatedToolCalls) if !done && !sawTerminal { return fmt.Errorf("deepinfra: stream ended before [DONE] or finish_reason") } diff --git a/internal/entity/models/futurmix.go b/internal/entity/models/futurmix.go index eb28958dc5..721f0468a6 100644 --- a/internal/entity/models/futurmix.go +++ b/internal/entity/models/futurmix.go @@ -221,35 +221,45 @@ func (f *FuturMixModel) ChatStreamlyWithSender(ctx context.Context, modelName st return fmt.Errorf("futurmix chat stream API error: %s, body: %s", resp.Status, string(body)) } - sawTerminal := false - done, err := ParseSSEStream[futurmixChatResponse](resp.Body, func(event futurmixChatResponse) error { - if len(event.Choices) == 0 { + accumulatedToolCalls := make(map[int]map[string]any) + if _, err = ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error { + choices, ok := event["choices"].([]interface{}) + if !ok || len(choices) == 0 { return nil } - choice := event.Choices[0] - if choice.Delta.ReasoningContent != "" { - r := choice.Delta.ReasoningContent - if err := sender(nil, &r); err != nil { + + firstChoice, ok := choices[0].(map[string]interface{}) + if !ok { + return nil + } + + delta, ok := firstChoice["delta"].(map[string]interface{}) + if !ok { + return nil + } + + accumulateToolCallDeltas(delta, accumulatedToolCalls) + + reasoningContent, ok := delta["reasoning_content"].(string) + if ok && reasoningContent != "" { + if err = sender(nil, &reasoningContent); err != nil { return err } } - if choice.Delta.Content != "" { - c := choice.Delta.Content - if err := sender(&c, nil); err != nil { + + content, ok := delta["content"].(string) + if ok && content != "" { + if err = sender(&content, nil); err != nil { return err } } - if choice.FinishReason != "" { - sawTerminal = true - } + return nil - }) - if err != nil { + }); err != nil { return fmt.Errorf("failed to scan response body: %w", err) } - if !done && !sawTerminal { - return fmt.Errorf("futurmix: stream ended before [DONE] or finish_reason") - } + + setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls) endOfStream := "[DONE]" return sender(&endOfStream, nil) diff --git a/internal/entity/models/gitee.go b/internal/entity/models/gitee.go index 391e759643..d89df8a4b4 100644 --- a/internal/entity/models/gitee.go +++ b/internal/entity/models/gitee.go @@ -25,7 +25,6 @@ import ( "mime/multipart" "net/http" "ragflow/internal/common" - "sort" "strings" "time" ) @@ -264,6 +263,8 @@ func (g *GiteeModel) ChatStreamlyWithSender(ctx context.Context, modelName strin return nil } + accumulateToolCallDeltas(delta, accumulatedToolCalls) + content, ok := delta["content"].(string) if ok && content != "" { common.Info(content) @@ -296,56 +297,6 @@ func (g *GiteeModel) ChatStreamlyWithSender(ctx context.Context, modelName strin } } - if tcs, ok := delta["tool_calls"].([]interface{}); ok { - for _, tc := range tcs { - tcMap, ok := tc.(map[string]interface{}) - if !ok { - continue - } - idxF, ok := tcMap["index"].(float64) - if !ok { - continue - } - idx := int(idxF) - existing, hasExisting := accumulatedToolCalls[idx] - if !hasExisting { - accumulatedToolCalls[idx] = cloneMap(tcMap) - continue - } - if id, ok := tcMap["id"].(string); ok && id != "" { - if eid, ok := existing["id"].(string); ok { - existing["id"] = eid + id - } else { - existing["id"] = id - } - } - if typ, ok := tcMap["type"].(string); ok && typ != "" { - existing["type"] = typ - } - if fn, ok := tcMap["function"].(map[string]interface{}); ok { - ef, ok := existing["function"].(map[string]interface{}) - if !ok { - ef = make(map[string]interface{}) - existing["function"] = ef - } - if name, ok := fn["name"].(string); ok && name != "" { - if en, ok := ef["name"].(string); ok { - ef["name"] = en + name - } else { - ef["name"] = name - } - } - if args, ok := fn["arguments"].(string); ok && args != "" { - if ea, ok := ef["arguments"].(string); ok { - ef["arguments"] = ea + args - } else { - ef["arguments"] = args - } - } - } - } - } - finishReason, ok := firstChoice["finish_reason"].(string) if ok && finishReason != "" { sawTerminal = true @@ -365,18 +316,7 @@ func (g *GiteeModel) ChatStreamlyWithSender(ctx context.Context, modelName strin } } - if len(accumulatedToolCalls) > 0 && chatModelConfig != nil { - indices := make([]int, 0, len(accumulatedToolCalls)) - for idx := range accumulatedToolCalls { - indices = append(indices, idx) - } - sort.Ints(indices) - tcs := make([]map[string]interface{}, 0, len(accumulatedToolCalls)) - for _, idx := range indices { - tcs = append(tcs, accumulatedToolCalls[idx]) - } - chatModelConfig.ToolCallsResult = &tcs - } + setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls) // Send [DONE] marker for OpenAI compatibility endOfStream := "[DONE]" diff --git a/internal/entity/models/google.go b/internal/entity/models/google.go index eb01866175..dc2bb99baa 100644 --- a/internal/entity/models/google.go +++ b/internal/entity/models/google.go @@ -18,6 +18,7 @@ package models import ( "context" + "encoding/json" "fmt" "ragflow/internal/common" "strings" @@ -119,6 +120,240 @@ func (g *GoogleModel) baseURL(apiConfig *APIConfig) string { return strings.TrimSpace(baseURL) } +func googleChatContents(messages []Message) []*genai.Content { + var contents []*genai.Content + toolCallNames := make(map[string]string) + + for _, msg := range messages { + switch msg.Role { + case "tool": + name := toolCallNames[msg.ToolCallID] + if name == "" { + name = msg.ToolCallID + } + contents = append(contents, &genai.Content{ + Role: genai.RoleUser, + Parts: []*genai.Part{{ + FunctionResponse: &genai.FunctionResponse{ + ID: msg.ToolCallID, + Name: name, + Response: googleFunctionResponse(msg.Content), + }, + }}, + }) + continue + } + + var role genai.Role + switch msg.Role { + case "model", "assistant": + role = genai.RoleModel + default: + role = genai.RoleUser + } + + parts := googleMessageParts(msg.Content) + for _, toolCall := range msg.ToolCalls { + id, _ := toolCall["id"].(string) + fn, _ := toolCall["function"].(map[string]interface{}) + name, _ := fn["name"].(string) + if name == "" { + continue + } + args := map[string]any{} + if arguments, ok := fn["arguments"].(string); ok && strings.TrimSpace(arguments) != "" { + _ = json.Unmarshal([]byte(arguments), &args) + } + if id != "" { + toolCallNames[id] = name + } + parts = append(parts, &genai.Part{FunctionCall: &genai.FunctionCall{ + ID: id, + Name: name, + Args: args, + }}) + } + if len(parts) > 0 { + contents = append(contents, genai.NewContentFromParts(parts, role)) + } + } + + return contents +} + +func googleMessageParts(content interface{}) []*genai.Part { + switch c := content.(type) { + case string: + if c == "" { + return nil + } + return []*genai.Part{genai.NewPartFromText(c)} + case []interface{}: + var parts []*genai.Part + for _, item := range c { + itemMap, ok := item.(map[string]interface{}) + if !ok { + continue + } + contentType, _ := itemMap["type"].(string) + switch contentType { + case "text": + if text, ok := itemMap["text"].(string); ok && text != "" { + parts = append(parts, genai.NewPartFromText(text)) + } + case "image_url": + if imgMap, ok := itemMap["image_url"].(map[string]interface{}); ok { + if url, ok := imgMap["url"].(string); ok && url != "" { + parts = append(parts, genai.NewPartFromURI(url, "image/jpeg")) + } + } + } + } + return parts + default: + return nil + } +} + +func googleFunctionResponse(content interface{}) map[string]any { + switch c := content.(type) { + case map[string]any: + return c + case string: + var response map[string]any + if err := json.Unmarshal([]byte(c), &response); err == nil && response != nil { + return response + } + return map[string]any{"output": c} + default: + return map[string]any{"output": c} + } +} + +func googleGenerateContentConfig(chatModelConfig *ChatConfig) *genai.GenerateContentConfig { + if chatModelConfig == nil { + return nil + } + + cfg := &genai.GenerateContentConfig{} + if chatModelConfig.Temperature != nil { + value := float32(*chatModelConfig.Temperature) + cfg.Temperature = &value + } + if chatModelConfig.TopP != nil { + value := float32(*chatModelConfig.TopP) + cfg.TopP = &value + } + if chatModelConfig.MaxTokens != nil { + cfg.MaxOutputTokens = int32(*chatModelConfig.MaxTokens) + } + if chatModelConfig.Stop != nil { + cfg.StopSequences = *chatModelConfig.Stop + } + if tools := googleTools(chatModelConfig.Tools); len(tools) > 0 { + cfg.Tools = tools + cfg.ToolConfig = &genai.ToolConfig{ + FunctionCallingConfig: &genai.FunctionCallingConfig{Mode: googleFunctionCallingMode(chatModelConfig.ToolChoice)}, + } + } + + if cfg.Temperature == nil && cfg.TopP == nil && cfg.MaxOutputTokens == 0 && len(cfg.StopSequences) == 0 && len(cfg.Tools) == 0 { + return nil + } + return cfg +} + +func googleFunctionCallingMode(toolChoice *string) genai.FunctionCallingConfigMode { + if toolChoice == nil { + return genai.FunctionCallingConfigModeAuto + } + switch strings.ToLower(strings.TrimSpace(*toolChoice)) { + case "none": + return genai.FunctionCallingConfigModeNone + case "required", "any": + return genai.FunctionCallingConfigModeAny + default: + return genai.FunctionCallingConfigModeAuto + } +} + +func googleTools(rawTools interface{}) []*genai.Tool { + var declarations []*genai.FunctionDeclaration + for _, rawTool := range normalizeToolList(rawTools) { + toolMap, ok := rawTool.(map[string]interface{}) + if !ok { + continue + } + fn, ok := toolMap["function"].(map[string]interface{}) + if !ok { + fn = toolMap + } + name, _ := fn["name"].(string) + if name == "" { + continue + } + description, _ := fn["description"].(string) + declaration := &genai.FunctionDeclaration{ + Name: name, + Description: description, + } + if parameters, ok := fn["parameters"]; ok { + declaration.ParametersJsonSchema = parameters + } + declarations = append(declarations, declaration) + } + if len(declarations) == 0 { + return nil + } + return []*genai.Tool{{FunctionDeclarations: declarations}} +} + +func normalizeToolList(rawTools interface{}) []interface{} { + switch tools := rawTools.(type) { + case nil: + return nil + case []interface{}: + return tools + case []map[string]interface{}: + result := make([]interface{}, 0, len(tools)) + for _, tool := range tools { + result = append(result, tool) + } + return result + default: + return nil + } +} + +func googleToolCalls(functionCalls []*genai.FunctionCall) []map[string]interface{} { + if len(functionCalls) == 0 { + return nil + } + toolCalls := make([]map[string]interface{}, 0, len(functionCalls)) + for idx, functionCall := range functionCalls { + if functionCall == nil || functionCall.Name == "" { + continue + } + id := functionCall.ID + if id == "" { + id = fmt.Sprintf("gemini-call-%d", idx) + } + arguments, err := json.Marshal(functionCall.Args) + if err != nil { + arguments = []byte("{}") + } + toolCalls = append(toolCalls, map[string]interface{}{ + "id": id, + "type": "function", + "function": map[string]interface{}{ + "name": functionCall.Name, + "arguments": string(arguments), + }, + }) + } + return toolCalls +} + func (g *GoogleModel) ChatWithMessages(ctx context.Context, modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, modelUsage *common.ModelUsage) (*ChatResponse, error) { if err := g.baseModel.APIConfigCheck(apiConfig); err != nil { return nil, err @@ -136,51 +371,10 @@ func (g *GoogleModel) ChatWithMessages(ctx context.Context, modelName string, me return nil, err } - // Convert messages to Google SDK format - var contents []*genai.Content - for _, msg := range messages { - var role genai.Role - switch msg.Role { - case "user": - role = genai.RoleUser - case "model", "assistant": - role = genai.RoleModel - default: - role = genai.RoleUser - } - - // Handle content based on type - switch c := msg.Content.(type) { - case string: - contents = append(contents, genai.NewContentFromText(c, role)) - case []interface{}: - // Multimodal content - group parts within a single content - var parts []*genai.Part - for _, item := range c { - if itemMap, ok := item.(map[string]interface{}); ok { - contentType, _ := itemMap["type"].(string) - switch contentType { - case "text": - if text, ok := itemMap["text"].(string); ok { - parts = append(parts, genai.NewPartFromText(text)) - } - case "image_url": - if imgMap, ok := itemMap["image_url"].(map[string]interface{}); ok { - if url, ok := imgMap["url"].(string); ok { - parts = append(parts, genai.NewPartFromURI(url, "image/jpeg")) - } - } - } - } - } - if len(parts) > 0 { - contents = append(contents, genai.NewContentFromParts(parts, role)) - } - } - } + contents := googleChatContents(messages) // Generate content (non-streaming) - response, err := client.Models.GenerateContent(ctx, modelName, contents, nil) + response, err := client.Models.GenerateContent(ctx, modelName, contents, googleGenerateContentConfig(chatModelConfig)) if err != nil { return nil, err } @@ -188,7 +382,7 @@ func (g *GoogleModel) ChatWithMessages(ctx context.Context, modelName string, me // Extract text from response answer := response.Text() - return &ChatResponse{Answer: &answer}, nil + return &ChatResponse{Answer: &answer, ToolCalls: googleToolCalls(response.FunctionCalls())}, nil } // ChatStreamlyWithSender sends messages and streams response via sender function (best performance, no channel) @@ -212,59 +406,21 @@ func (g *GoogleModel) ChatStreamlyWithSender(ctx context.Context, modelName stri return err } - // Convert messages to Google SDK format - var contents []*genai.Content - for _, msg := range messages { - var role genai.Role - switch msg.Role { - case "user": - role = genai.RoleUser - case "model", "assistant": - role = genai.RoleModel - default: - role = genai.RoleUser - } - - // Handle content based on type - switch c := msg.Content.(type) { - case string: - contents = append(contents, genai.NewContentFromText(c, role)) - case []interface{}: - // Multimodal content - group parts within a single content - var parts []*genai.Part - for _, item := range c { - if itemMap, ok := item.(map[string]interface{}); ok { - contentType, _ := itemMap["type"].(string) - switch contentType { - case "text": - if text, ok := itemMap["text"].(string); ok { - parts = append(parts, genai.NewPartFromText(text)) - } - case "image_url": - if imgMap, ok := itemMap["image_url"].(map[string]interface{}); ok { - if url, ok := imgMap["url"].(string); ok { - parts = append(parts, genai.NewPartFromURI(url, "image/jpeg")) - } - } - } - } - } - if len(parts) > 0 { - contents = append(contents, genai.NewContentFromParts(parts, role)) - } - } - } + contents := googleChatContents(messages) + var toolCalls []map[string]interface{} for response, err := range client.Models.GenerateContentStream( ctx, modelName, contents, - nil, + googleGenerateContentConfig(chatModelConfig), ) { if err != nil { return err } + toolCalls = append(toolCalls, googleToolCalls(response.FunctionCalls())...) + content := response.Text() var responseContent string @@ -291,6 +447,10 @@ func (g *GoogleModel) ChatStreamlyWithSender(ctx context.Context, modelName stri } } + if chatModelConfig != nil && len(toolCalls) > 0 { + chatModelConfig.ToolCallsResult = &toolCalls + } + return err } diff --git a/internal/entity/models/google_test.go b/internal/entity/models/google_test.go index d441ff0b30..9ad8552837 100644 --- a/internal/entity/models/google_test.go +++ b/internal/entity/models/google_test.go @@ -2,6 +2,7 @@ package models import ( "context" + "encoding/json" "errors" "reflect" "strings" @@ -391,6 +392,103 @@ func TestCollectGoogleModelNamesReturnsPageError(t *testing.T) { } } +func TestGoogleGenerateContentConfigConvertsTools(t *testing.T) { + toolChoice := "required" + cfg := googleGenerateContentConfig(&ChatConfig{ + Tools: []map[string]interface{}{{ + "type": "function", + "function": map[string]interface{}{ + "name": "search_my_dataset", + "description": "Search dataset.", + "parameters": map[string]interface{}{ + "type": "object", + "properties": map[string]interface{}{ + "query": map[string]interface{}{"type": "string"}, + }, + "required": []string{"query"}, + }, + }, + }}, + ToolChoice: &toolChoice, + }) + if cfg == nil || len(cfg.Tools) != 1 || len(cfg.Tools[0].FunctionDeclarations) != 1 { + t.Fatalf("tools = %#v, want one function declaration", cfg) + } + declaration := cfg.Tools[0].FunctionDeclarations[0] + if declaration.Name != "search_my_dataset" || declaration.Description != "Search dataset." { + t.Fatalf("declaration = %#v", declaration) + } + if declaration.ParametersJsonSchema == nil { + t.Fatal("ParametersJsonSchema is nil") + } + if cfg.ToolConfig == nil || cfg.ToolConfig.FunctionCallingConfig == nil { + t.Fatalf("ToolConfig = %#v", cfg.ToolConfig) + } + if cfg.ToolConfig.FunctionCallingConfig.Mode != genai.FunctionCallingConfigModeAny { + t.Fatalf("mode = %s, want ANY", cfg.ToolConfig.FunctionCallingConfig.Mode) + } +} + +func TestGoogleChatContentsConvertsToolHistory(t *testing.T) { + contents := googleChatContents([]Message{ + { + Role: "assistant", + Content: nil, + ToolCalls: []map[string]interface{}{{ + "id": "call-1", + "type": "function", + "function": map[string]interface{}{ + "name": "search_my_dataset", + "arguments": `{"query":"marigold"}`, + }, + }}, + }, + {Role: "tool", ToolCallID: "call-1", Content: "flower result"}, + }) + if len(contents) != 2 { + t.Fatalf("contents len = %d, want 2", len(contents)) + } + functionCall := contents[0].Parts[0].FunctionCall + if functionCall == nil || functionCall.ID != "call-1" || functionCall.Name != "search_my_dataset" { + t.Fatalf("function call = %#v", functionCall) + } + if functionCall.Args["query"] != "marigold" { + t.Fatalf("args = %#v", functionCall.Args) + } + functionResponse := contents[1].Parts[0].FunctionResponse + if functionResponse == nil || functionResponse.ID != "call-1" || functionResponse.Name != "search_my_dataset" { + t.Fatalf("function response = %#v", functionResponse) + } + if functionResponse.Response["output"] != "flower result" { + t.Fatalf("response = %#v", functionResponse.Response) + } +} + +func TestGoogleToolCallsConvertsFunctionCalls(t *testing.T) { + toolCalls := googleToolCalls([]*genai.FunctionCall{{ + ID: "call-1", + Name: "search_my_dataset", + Args: map[string]any{"query": "marigold"}, + }}) + if len(toolCalls) != 1 { + t.Fatalf("tool calls len = %d, want 1", len(toolCalls)) + } + if toolCalls[0]["id"] != "call-1" || toolCalls[0]["type"] != "function" { + t.Fatalf("tool call = %#v", toolCalls[0]) + } + function, _ := toolCalls[0]["function"].(map[string]interface{}) + if function["name"] != "search_my_dataset" { + t.Fatalf("function = %#v", function) + } + var args map[string]interface{} + if err := json.Unmarshal([]byte(function["arguments"].(string)), &args); err != nil { + t.Fatalf("arguments JSON: %v", err) + } + if args["query"] != "marigold" { + t.Fatalf("arguments = %#v", args) + } +} + func stringPtr(value string) *string { return &value } diff --git a/internal/entity/models/gpustack.go b/internal/entity/models/gpustack.go index 723b783166..95460b0e2f 100644 --- a/internal/entity/models/gpustack.go +++ b/internal/entity/models/gpustack.go @@ -120,7 +120,8 @@ func (g *GPUStackModel) ChatWithMessages(ctx context.Context, modelName string, return nil, fmt.Errorf("invalid message format") } content, ok := messageMap["content"].(string) - if !ok { + toolCalls := extractToolCalls(messageMap) + if !ok && len(toolCalls) == 0 { return nil, fmt.Errorf("invalid content format") } @@ -132,6 +133,7 @@ func (g *GPUStackModel) ChatWithMessages(ctx context.Context, modelName string, return &ChatResponse{ Answer: &content, ReasonContent: &reasonContent, + ToolCalls: toolCalls, }, nil } @@ -189,6 +191,7 @@ func (g *GPUStackModel) ChatStreamlyWithSender(ctx context.Context, modelName st } sawTerminal := false + accumulatedToolCalls := make(map[int]map[string]any) done, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error { if apiErr, ok := event["error"]; ok { return fmt.Errorf("gpustack: upstream stream error: %v", apiErr) @@ -207,6 +210,8 @@ func (g *GPUStackModel) ChatStreamlyWithSender(ctx context.Context, modelName st return nil } + accumulateToolCallDeltas(delta, accumulatedToolCalls) + if r, ok := delta["reasoning_content"].(string); ok && r != "" { if err := sender(nil, &r); err != nil { return err @@ -229,6 +234,7 @@ func (g *GPUStackModel) ChatStreamlyWithSender(ctx context.Context, modelName st if err != nil { return fmt.Errorf("failed to scan response body: %w", err) } + setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls) if !done && !sawTerminal { return fmt.Errorf("gpustack: stream ended before [DONE] or finish_reason") } diff --git a/internal/entity/models/groq.go b/internal/entity/models/groq.go index f1eb77bb7c..9ea214324b 100644 --- a/internal/entity/models/groq.go +++ b/internal/entity/models/groq.go @@ -216,30 +216,51 @@ func (g *GroqModel) ChatStreamlyWithSender(ctx context.Context, modelName string } sawTerminal := false - done, err := ParseSSEStream[groqChatResponse](resp.Body, func(event groqChatResponse) error { - if event.Error != nil { - return fmt.Errorf("groq: upstream stream error: %v", event.Error) + accumulatedToolCalls := make(map[int]map[string]interface{}) + done, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error { + common.Info(fmt.Sprintf("%v", event)) + + tokenUsage, found, usageErr := decodeOpenAICompatibleStreamUsage(event) + if usageErr != nil { + return usageErr } - if len(event.Choices) == 0 { + if found { + applyStreamUsage(chatModelConfig, modelUsage, tokenUsage) + } + + choices, ok := event["choices"].([]interface{}) + if !ok || len(choices) == 0 { return nil } - choice := event.Choices[0] - reasoning := choice.Delta.ReasoningContent - if reasoning == "" { - reasoning = choice.Delta.Reasoning + firstChoice, ok := choices[0].(map[string]interface{}) + if !ok { + return nil } - if reasoning != "" { - if err := sender(nil, &reasoning); err != nil { + + delta, ok := firstChoice["delta"].(map[string]interface{}) + if !ok { + return nil + } + + accumulateToolCallDeltas(delta, accumulatedToolCalls) + + content, ok := delta["content"].(string) + if ok && content != "" { + if err := sender(&content, nil); err != nil { return err } } - if choice.Delta.Content != "" { - if err := sender(&choice.Delta.Content, nil); err != nil { + + reasoningContent, ok := delta["reasoning_content"].(string) + if ok && reasoningContent != "" { + if err := sender(nil, &reasoningContent); err != nil { return err } } - if choice.FinishReason != "" || event.FinishReason != "" { + + finishReason, ok := firstChoice["finish_reason"].(string) + if ok && finishReason != "" { sawTerminal = true } return nil @@ -248,9 +269,10 @@ func (g *GroqModel) ChatStreamlyWithSender(ctx context.Context, modelName string return fmt.Errorf("failed to scan response body: %w", err) } if !done && !sawTerminal { - return fmt.Errorf("groq: stream ended before [DONE] or finish_reason") + return fmt.Errorf("deepseek: stream ended before [DONE] or finish_reason") } + setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls) endOfStream := "[DONE]" return sender(&endOfStream, nil) } diff --git a/internal/entity/models/huggingface.go b/internal/entity/models/huggingface.go index 4e7373c19e..f5c674989c 100644 --- a/internal/entity/models/huggingface.go +++ b/internal/entity/models/huggingface.go @@ -133,7 +133,8 @@ func (h *HuggingFaceModel) ChatWithMessages(ctx context.Context, modelName strin } content, ok := messageMap["content"].(string) - if !ok { + toolCalls := extractToolCalls(messageMap) + if !ok && len(toolCalls) == 0 { return nil, fmt.Errorf("invalid content format") } @@ -152,6 +153,7 @@ func (h *HuggingFaceModel) ChatWithMessages(ctx context.Context, modelName strin chatResponse := &ChatResponse{ Answer: &content, ReasonContent: &reasonContent, + ToolCalls: toolCalls, } return chatResponse, nil @@ -212,6 +214,7 @@ func (h *HuggingFaceModel) ChatStreamlyWithSender(ctx context.Context, modelName return fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body)) } + accumulatedToolCalls := make(map[int]map[string]any) if _, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error { common.Info(fmt.Sprintf("%v", event)) @@ -230,6 +233,8 @@ func (h *HuggingFaceModel) ChatStreamlyWithSender(ctx context.Context, modelName return nil } + accumulateToolCallDeltas(delta, accumulatedToolCalls) + reasoningContent, ok := delta["reasoning_content"].(string) if ok && reasoningContent != "" { if err := sender(nil, &reasoningContent); err != nil { @@ -248,6 +253,7 @@ func (h *HuggingFaceModel) ChatStreamlyWithSender(ctx context.Context, modelName }); err != nil { return fmt.Errorf("failed to scan response body: %w", err) } + setSortedToolCallsResult(modelConfig, accumulatedToolCalls) // Send [DONE] marker for OpenAI compatibility endOfStream := "[DONE]" diff --git a/internal/entity/models/hunyuan.go b/internal/entity/models/hunyuan.go index 3a5a9c5061..4ce824e322 100644 --- a/internal/entity/models/hunyuan.go +++ b/internal/entity/models/hunyuan.go @@ -117,7 +117,8 @@ func (h *HunyuanModel) ChatWithMessages(ctx context.Context, modelName string, m } content, ok := messageMap["content"].(string) - if !ok { + toolCalls := extractToolCalls(messageMap) + if !ok && len(toolCalls) == 0 { return nil, fmt.Errorf("invalid content format") } @@ -129,6 +130,7 @@ func (h *HunyuanModel) ChatWithMessages(ctx context.Context, modelName string, m return &ChatResponse{ Answer: &content, ReasonContent: &reasonContent, + ToolCalls: toolCalls, }, nil } @@ -180,6 +182,7 @@ func (h *HunyuanModel) ChatStreamlyWithSender(ctx context.Context, modelName str } sawTerminal := false + accumulatedToolCalls := make(map[int]map[string]any) done, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error { if apiErr, ok := event["error"]; ok { return fmt.Errorf("hunyuan: upstream stream error: %v", apiErr) @@ -194,6 +197,7 @@ func (h *HunyuanModel) ChatStreamlyWithSender(ctx context.Context, modelName str return nil } if delta, ok := firstChoice["delta"].(map[string]interface{}); ok { + accumulateToolCallDeltas(delta, accumulatedToolCalls) if r, ok := delta["reasoning_content"].(string); ok && r != "" { rr := r if err := sender(nil, &rr); err != nil { @@ -215,6 +219,7 @@ func (h *HunyuanModel) ChatStreamlyWithSender(ctx context.Context, modelName str if err != nil { return fmt.Errorf("failed to scan response body: %w", err) } + setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls) if !done && !sawTerminal { return fmt.Errorf("hunyuan: stream ended before [DONE] or finish_reason") } diff --git a/internal/entity/models/jiekouai.go b/internal/entity/models/jiekouai.go index bfdfe037af..05d5501cf0 100644 --- a/internal/entity/models/jiekouai.go +++ b/internal/entity/models/jiekouai.go @@ -143,7 +143,8 @@ func (j *JieKouAIModel) ChatWithMessages(ctx context.Context, modelName string, } content, ok := messageMap["content"].(string) - if !ok { + toolCalls := extractToolCalls(messageMap) + if !ok && len(toolCalls) == 0 { return nil, fmt.Errorf("invalid content format") } @@ -162,6 +163,7 @@ func (j *JieKouAIModel) ChatWithMessages(ctx context.Context, modelName string, chatResponse := &ChatResponse{ Answer: &content, ReasonContent: &reasonContent, + ToolCalls: toolCalls, } return chatResponse, nil @@ -225,6 +227,7 @@ func (j *JieKouAIModel) ChatStreamlyWithSender(ctx context.Context, modelName st } // SSE parsing: read line by line + accumulatedToolCalls := make(map[int]map[string]any) if _, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error { choices, ok := event["choices"].([]interface{}) if !ok || len(choices) == 0 { @@ -241,6 +244,8 @@ func (j *JieKouAIModel) ChatStreamlyWithSender(ctx context.Context, modelName st return nil } + accumulateToolCallDeltas(delta, accumulatedToolCalls) + reasoningContent, ok := delta["reasoning_content"].(string) if ok && reasoningContent != "" { if err := sender(nil, &reasoningContent); err != nil { @@ -259,6 +264,7 @@ func (j *JieKouAIModel) ChatStreamlyWithSender(ctx context.Context, modelName st }); err != nil { return fmt.Errorf("failed to scan response body: %w", err) } + setSortedToolCallsResult(modelConfig, accumulatedToolCalls) // Send [DONE] marker for OpenAI compatibility endOfStream := "[DONE]" diff --git a/internal/entity/models/lmstudio.go b/internal/entity/models/lmstudio.go index 2be20a0c63..30858da5f4 100644 --- a/internal/entity/models/lmstudio.go +++ b/internal/entity/models/lmstudio.go @@ -146,7 +146,8 @@ func (l *LmStudioModel) ChatWithMessages(ctx context.Context, modelName string, } content, ok := messageMap["content"].(string) - if !ok { + toolCalls := extractToolCalls(messageMap) + if !ok && len(toolCalls) == 0 { return nil, fmt.Errorf("invalid content format") } @@ -164,6 +165,7 @@ func (l *LmStudioModel) ChatWithMessages(ctx context.Context, modelName string, chatResponse := &ChatResponse{ Answer: &content, ReasonContent: &reasonContent, + ToolCalls: toolCalls, } return chatResponse, nil @@ -233,6 +235,7 @@ func (l *LmStudioModel) ChatStreamlyWithSender(ctx context.Context, modelName st } // SSE parsing: read line by line + accumulatedToolCalls := make(map[int]map[string]any) if _, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error { common.Info(fmt.Sprintf("%v", event)) @@ -251,6 +254,8 @@ func (l *LmStudioModel) ChatStreamlyWithSender(ctx context.Context, modelName st return nil } + accumulateToolCallDeltas(delta, accumulatedToolCalls) + reasoningContent, ok := delta["reasoning_content"].(string) if ok && reasoningContent != "" { if err := sender(nil, &reasoningContent); err != nil { @@ -269,6 +274,7 @@ func (l *LmStudioModel) ChatStreamlyWithSender(ctx context.Context, modelName st }); err != nil { return fmt.Errorf("failed to scan response body: %w", err) } + setSortedToolCallsResult(modelConfig, accumulatedToolCalls) // Send [DONE] marker for OpenAI compatibility endOfStream := "[DONE]" diff --git a/internal/entity/models/localai.go b/internal/entity/models/localai.go index e94b6f523e..2445cfdd5f 100644 --- a/internal/entity/models/localai.go +++ b/internal/entity/models/localai.go @@ -154,7 +154,8 @@ func (l *LocalAIModel) ChatWithMessages(ctx context.Context, modelName string, m } content, ok := messageMap["content"].(string) - if !ok { + toolCalls := extractToolCalls(messageMap) + if !ok && len(toolCalls) == 0 { return nil, fmt.Errorf("invalid content format") } @@ -165,6 +166,7 @@ func (l *LocalAIModel) ChatWithMessages(ctx context.Context, modelName string, m return &ChatResponse{ Answer: &content, ReasonContent: &reasonContent, + ToolCalls: toolCalls, }, nil } @@ -248,6 +250,7 @@ func (l *LocalAIModel) ChatStreamlyWithSender(ctx context.Context, modelName str }() sawTerminal := false + accumulatedToolCalls := make(map[int]map[string]any) streamDone, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error { lastActiveMu.Lock() lastActive = time.Now() @@ -268,6 +271,8 @@ func (l *LocalAIModel) ChatStreamlyWithSender(ctx context.Context, modelName str return nil } + accumulateToolCallDeltas(delta, accumulatedToolCalls) + if reasoning := extractLocalAIReasoning(delta); reasoning != "" { if err := sender(nil, &reasoning); err != nil { return err @@ -293,6 +298,7 @@ func (l *LocalAIModel) ChatStreamlyWithSender(ctx context.Context, modelName str } return fmt.Errorf("failed to scan response body: %w", err) } + setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls) if !streamDone && !sawTerminal { return fmt.Errorf("localai: stream ended before [DONE] or finish_reason") } diff --git a/internal/entity/models/longcat.go b/internal/entity/models/longcat.go index 08f295f09d..2827f3af64 100644 --- a/internal/entity/models/longcat.go +++ b/internal/entity/models/longcat.go @@ -123,7 +123,8 @@ func (l *LongCatModel) ChatWithMessages(ctx context.Context, modelName string, m } content, ok := messageMap["content"].(string) - if !ok { + toolCalls := extractToolCalls(messageMap) + if !ok && len(toolCalls) == 0 { return nil, fmt.Errorf("invalid content format") } @@ -140,6 +141,7 @@ func (l *LongCatModel) ChatWithMessages(ctx context.Context, modelName string, m return &ChatResponse{ Answer: &content, ReasonContent: &reasonContent, + ToolCalls: toolCalls, }, nil } @@ -200,6 +202,7 @@ func (l *LongCatModel) ChatStreamlyWithSender(ctx context.Context, modelName str } sawTerminal := false + accumulatedToolCalls := make(map[int]map[string]any) done, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error { if apiErr, ok := event["error"]; ok { return fmt.Errorf("longcat: upstream stream error: %v", apiErr) @@ -220,6 +223,8 @@ func (l *LongCatModel) ChatStreamlyWithSender(ctx context.Context, modelName str return nil } + accumulateToolCallDeltas(delta, accumulatedToolCalls) + if r, ok := delta["reasoning_content"].(string); ok && r != "" { if err := sender(nil, &r); err != nil { return err @@ -242,6 +247,7 @@ func (l *LongCatModel) ChatStreamlyWithSender(ctx context.Context, modelName str if err != nil { return fmt.Errorf("failed to scan response body: %w", err) } + setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls) if !done && !sawTerminal { return fmt.Errorf("longcat: stream ended before [DONE] or finish_reason") } diff --git a/internal/entity/models/mistral.go b/internal/entity/models/mistral.go index 133eca6dfe..ae48e99e7a 100644 --- a/internal/entity/models/mistral.go +++ b/internal/entity/models/mistral.go @@ -127,10 +127,12 @@ func (m *MistralModel) ChatWithMessages(ctx context.Context, modelName string, m if err != nil { return nil, err } + toolCalls := extractToolCalls(messageMap) return &ChatResponse{ Answer: &content, ReasonContent: &reasonContent, + ToolCalls: toolCalls, }, nil } @@ -226,6 +228,7 @@ func (m *MistralModel) ChatStreamlyWithSender(ctx context.Context, modelName str } sawTerminal := false + accumulatedToolCalls := make(map[int]map[string]any) done, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error { choices, ok := event["choices"].([]interface{}) if !ok || len(choices) == 0 { @@ -242,6 +245,8 @@ func (m *MistralModel) ChatStreamlyWithSender(ctx context.Context, modelName str return nil } + accumulateToolCallDeltas(delta, accumulatedToolCalls) + content, ok := delta["content"].(string) if ok && content != "" { if err := sender(&content, nil); err != nil { @@ -258,6 +263,7 @@ func (m *MistralModel) ChatStreamlyWithSender(ctx context.Context, modelName str if err != nil { return fmt.Errorf("failed to scan response body: %w", err) } + setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls) if !done && !sawTerminal { return fmt.Errorf("mistral: stream ended before [DONE] or finish_reason") } diff --git a/internal/entity/models/modelscope.go b/internal/entity/models/modelscope.go index a6fcf475d2..d1da4bc676 100644 --- a/internal/entity/models/modelscope.go +++ b/internal/entity/models/modelscope.go @@ -252,6 +252,7 @@ func (m *ModelScopeModel) ChatStreamlyWithSender(ctx context.Context, modelName }() sawTerminal := false + accumulatedToolCalls := make(map[int]map[string]any) streamDone, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error { lastActiveMu.Lock() lastActive = time.Now() @@ -267,6 +268,7 @@ func (m *ModelScopeModel) ChatStreamlyWithSender(ctx context.Context, modelName } if delta, ok := firstChoice["delta"].(map[string]interface{}); ok { + accumulateToolCallDeltas(delta, accumulatedToolCalls) if reasoning := modelscopeReasoningFromMap(delta); reasoning != "" { if err := sender(nil, &reasoning); err != nil { return err @@ -294,6 +296,8 @@ func (m *ModelScopeModel) ChatStreamlyWithSender(ctx context.Context, modelName return fmt.Errorf("modelscope: stream ended before [DONE] or finish_reason") } + setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls) + endOfStream := "[DONE]" return sender(&endOfStream, nil) } diff --git a/internal/entity/models/n1n.go b/internal/entity/models/n1n.go index 1fb07a3d8d..6ae530ae76 100644 --- a/internal/entity/models/n1n.go +++ b/internal/entity/models/n1n.go @@ -233,24 +233,51 @@ func (n *N1NModel) ChatStreamlyWithSender(ctx context.Context, modelName string, } sawTerminal := false - done, err := ParseSSEStream[n1nChatResponse](resp.Body, func(event n1nChatResponse) error { - if len(event.Choices) == 0 { + accumulatedToolCalls := make(map[int]map[string]interface{}) + done, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error { + common.Info(fmt.Sprintf("%v", event)) + + tokenUsage, found, usageErr := decodeOpenAICompatibleStreamUsage(event) + if usageErr != nil { + return usageErr + } + if found { + applyStreamUsage(chatModelConfig, modelUsage, tokenUsage) + } + + choices, ok := event["choices"].([]interface{}) + if !ok || len(choices) == 0 { return nil } - choice := event.Choices[0] - if choice.Delta.ReasoningContent != "" { - r := choice.Delta.ReasoningContent - if err := sender(nil, &r); err != nil { + + firstChoice, ok := choices[0].(map[string]interface{}) + if !ok { + return nil + } + + delta, ok := firstChoice["delta"].(map[string]interface{}) + if !ok { + return nil + } + + accumulateToolCallDeltas(delta, accumulatedToolCalls) + + content, ok := delta["content"].(string) + if ok && content != "" { + if err := sender(&content, nil); err != nil { return err } } - if choice.Delta.Content != "" { - c := choice.Delta.Content - if err := sender(&c, nil); err != nil { + + reasoningContent, ok := delta["reasoning_content"].(string) + if ok && reasoningContent != "" { + if err := sender(nil, &reasoningContent); err != nil { return err } } - if choice.FinishReason != "" { + + finishReason, ok := firstChoice["finish_reason"].(string) + if ok && finishReason != "" { sawTerminal = true } return nil @@ -259,9 +286,11 @@ func (n *N1NModel) ChatStreamlyWithSender(ctx context.Context, modelName string, return fmt.Errorf("failed to scan response body: %w", err) } if !done && !sawTerminal { - return fmt.Errorf("n1n: stream ended before [DONE] or finish_reason") + return fmt.Errorf("deepseek: stream ended before [DONE] or finish_reason") } + setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls) + endOfStream := "[DONE]" if err := sender(&endOfStream, nil); err != nil { return err diff --git a/internal/entity/models/novita.go b/internal/entity/models/novita.go index a3a6b5cd7d..1a7d128d38 100644 --- a/internal/entity/models/novita.go +++ b/internal/entity/models/novita.go @@ -256,7 +256,8 @@ func (n *NovitaModel) ChatWithMessages(ctx context.Context, modelName string, me } rawContent, ok := messageMap["content"].(string) - if !ok { + toolCalls := extractToolCalls(messageMap) + if !ok && len(toolCalls) == 0 { return nil, fmt.Errorf("invalid content format") } @@ -269,18 +270,22 @@ func (n *NovitaModel) ChatWithMessages(ctx context.Context, modelName string, me // `reasoning_content` field, with `content` already cleaned. // Handle both so the visible Answer is always tag-free and any // reasoning the upstream supplied is preserved. - visible, reasoning := splitNovitaThink(rawContent) - if r, ok := messageMap["reasoning_content"].(string); ok && r != "" { - if reasoning != "" { - reasoning += "\n" + r - } else { - reasoning = r + visible, reasoning := "", "" + if ok { + visible, reasoning = splitNovitaThink(rawContent) + if r, ok := messageMap["reasoning_content"].(string); ok && r != "" { + if reasoning != "" { + reasoning += "\n" + r + } else { + reasoning = r + } } } return &ChatResponse{ Answer: &visible, ReasonContent: &reasoning, + ToolCalls: toolCalls, }, nil } diff --git a/internal/entity/models/nvidia.go b/internal/entity/models/nvidia.go index 89ce5a05a8..e469c60a1b 100644 --- a/internal/entity/models/nvidia.go +++ b/internal/entity/models/nvidia.go @@ -133,7 +133,8 @@ func (n *NvidiaModel) ChatWithMessages(ctx context.Context, modelName string, me } content, ok := messageMap["content"].(string) - if !ok { + toolCalls := extractToolCalls(messageMap) + if !ok && len(toolCalls) == 0 { return nil, fmt.Errorf("invalid content format") } @@ -151,6 +152,7 @@ func (n *NvidiaModel) ChatWithMessages(ctx context.Context, modelName string, me chatResponse := &ChatResponse{ Answer: &content, ReasonContent: &reasonContent, + ToolCalls: toolCalls, } return chatResponse, nil @@ -215,6 +217,7 @@ func (n *NvidiaModel) ChatStreamlyWithSender(ctx context.Context, modelName stri return fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body)) } + accumulatedToolCalls := make(map[int]map[string]any) if _, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error { choices, ok := event["choices"].([]interface{}) if !ok || len(choices) == 0 { @@ -231,6 +234,8 @@ func (n *NvidiaModel) ChatStreamlyWithSender(ctx context.Context, modelName stri return nil } + accumulateToolCallDeltas(delta, accumulatedToolCalls) + reasoningContent, ok := delta["reasoning_content"].(string) if ok && reasoningContent != "" { if err := sender(nil, &reasoningContent); err != nil { @@ -250,6 +255,8 @@ func (n *NvidiaModel) ChatStreamlyWithSender(ctx context.Context, modelName stri return fmt.Errorf("failed to scan response body: %w", err) } + setSortedToolCallsResult(modelConfig, accumulatedToolCalls) + endOfStream := "[DONE]" if err = sender(&endOfStream, nil); err != nil { return err diff --git a/internal/entity/models/openrouter.go b/internal/entity/models/openrouter.go index c0b7c1c56d..81ee38b08c 100644 --- a/internal/entity/models/openrouter.go +++ b/internal/entity/models/openrouter.go @@ -130,7 +130,8 @@ func (o *OpenRouterModel) ChatWithMessages(ctx context.Context, modelName string } content, ok := messageMap["content"].(string) - if !ok { + toolCalls := extractToolCalls(messageMap) + if !ok && len(toolCalls) == 0 { return nil, fmt.Errorf("no message in response") } @@ -148,6 +149,7 @@ func (o *OpenRouterModel) ChatWithMessages(ctx context.Context, modelName string chatResponse := &ChatResponse{ Answer: &content, ReasonContent: &reasonContent, + ToolCalls: toolCalls, } return chatResponse, nil @@ -210,6 +212,7 @@ func (o *OpenRouterModel) ChatStreamlyWithSender(ctx context.Context, modelName return fmt.Errorf("invalid status code: %d, body: %s", resp.StatusCode, string(body)) } + accumulatedToolCalls := make(map[int]map[string]any) if _, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error { common.Info(fmt.Sprintf("%v", event)) @@ -228,6 +231,8 @@ func (o *OpenRouterModel) ChatStreamlyWithSender(ctx context.Context, modelName return nil } + accumulateToolCallDeltas(delta, accumulatedToolCalls) + reasoningContent, ok := delta["reasoning"].(string) if ok && reasoningContent != "" { if err := sender(nil, &reasoningContent); err != nil { @@ -246,6 +251,7 @@ func (o *OpenRouterModel) ChatStreamlyWithSender(ctx context.Context, modelName }); err != nil { return fmt.Errorf("failed to scan response body: %w", err) } + setSortedToolCallsResult(modelConfig, accumulatedToolCalls) // Send [DONE] marker for OpenAI compatibility endOfStream := "[DONE]" diff --git a/internal/entity/models/orcarouter.go b/internal/entity/models/orcarouter.go index a7a23325a0..36b1fbd37a 100644 --- a/internal/entity/models/orcarouter.go +++ b/internal/entity/models/orcarouter.go @@ -188,6 +188,7 @@ func (o *OrcaRouterModel) ChatStreamlyWithSender(ctx context.Context, modelName // SSE parsing: read line by line sawTerminal := false + accumulatedToolCalls := make(map[int]map[string]any) done, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error { common.Info(fmt.Sprintf("%v", event)) @@ -206,6 +207,8 @@ func (o *OrcaRouterModel) ChatStreamlyWithSender(ctx context.Context, modelName return nil } + accumulateToolCallDeltas(delta, accumulatedToolCalls) + content, ok := delta["content"].(string) if ok && content != "" { if err := sender(&content, nil); err != nil { @@ -222,6 +225,7 @@ func (o *OrcaRouterModel) ChatStreamlyWithSender(ctx context.Context, modelName if err != nil { return fmt.Errorf("failed to scan response body: %w", err) } + setSortedToolCallsResult(modelConfig, accumulatedToolCalls) _ = done _ = sawTerminal diff --git a/internal/entity/models/stepfun.go b/internal/entity/models/stepfun.go index 64e7e4d087..d6fc736406 100644 --- a/internal/entity/models/stepfun.go +++ b/internal/entity/models/stepfun.go @@ -122,7 +122,8 @@ func (s *StepFunModel) ChatWithMessages(ctx context.Context, modelName string, m } content, ok := messageMap["content"].(string) - if !ok { + toolCalls := extractToolCalls(messageMap) + if !ok && len(toolCalls) == 0 { return nil, fmt.Errorf("invalid content format") } @@ -180,6 +181,7 @@ func (s *StepFunModel) ChatStreamlyWithSender(ctx context.Context, modelName str } sawTerminal := false + accumulatedToolCalls := make(map[int]map[string]any) done, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error { choices, ok := event["choices"].([]interface{}) if !ok || len(choices) == 0 { @@ -196,6 +198,8 @@ func (s *StepFunModel) ChatStreamlyWithSender(ctx context.Context, modelName str return nil } + accumulateToolCallDeltas(delta, accumulatedToolCalls) + content, ok := delta["content"].(string) if ok && content != "" { if err := sender(&content, nil); err != nil { @@ -212,6 +216,7 @@ func (s *StepFunModel) ChatStreamlyWithSender(ctx context.Context, modelName str if err != nil { return fmt.Errorf("failed to scan response body: %w", err) } + setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls) if !done && !sawTerminal { return fmt.Errorf("stepfun: stream ended before [DONE] or finish_reason") } diff --git a/internal/entity/models/tokenhub.go b/internal/entity/models/tokenhub.go index 46677a9301..6524f49806 100644 --- a/internal/entity/models/tokenhub.go +++ b/internal/entity/models/tokenhub.go @@ -127,7 +127,8 @@ func (t *TokenHubModel) ChatWithMessages(ctx context.Context, modelName string, } content, ok := messageMap["content"].(string) - if !ok { + toolCalls := extractToolCalls(messageMap) + if !ok && len(toolCalls) == 0 { return nil, fmt.Errorf("invalid content format") } @@ -145,6 +146,7 @@ func (t *TokenHubModel) ChatWithMessages(ctx context.Context, modelName string, chatResponse := &ChatResponse{ Answer: &content, ReasonContent: &reasonContent, + ToolCalls: toolCalls, } return chatResponse, nil @@ -199,6 +201,7 @@ func (t *TokenHubModel) ChatStreamlyWithSender(ctx context.Context, modelName st return fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body)) } + accumulatedToolCalls := make(map[int]map[string]any) if _, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error { choices, ok := event["choices"].([]interface{}) if !ok || len(choices) == 0 { @@ -215,6 +218,8 @@ func (t *TokenHubModel) ChatStreamlyWithSender(ctx context.Context, modelName st return nil } + accumulateToolCallDeltas(delta, accumulatedToolCalls) + reasoningContent, ok := delta["reasoning_content"].(string) if !ok || reasoningContent == "" { reasoningContent, _ = delta["reasoning"].(string) @@ -237,6 +242,7 @@ func (t *TokenHubModel) ChatStreamlyWithSender(ctx context.Context, modelName st }); err != nil { return fmt.Errorf("failed to scan response body: %w", err) } + setSortedToolCallsResult(modelConfig, accumulatedToolCalls) // Send [DONE] marker for OpenAI compatibility endOfStream := "[DONE]" diff --git a/internal/entity/models/tokenpony.go b/internal/entity/models/tokenpony.go index 2dfa4bd2ed..d87dda67fc 100644 --- a/internal/entity/models/tokenpony.go +++ b/internal/entity/models/tokenpony.go @@ -118,7 +118,8 @@ func (t *TokenPonyModel) ChatWithMessages(ctx context.Context, modelName string, } content, ok := messageMap["content"].(string) - if !ok { + toolCalls := extractToolCalls(messageMap) + if !ok && len(toolCalls) == 0 { return nil, fmt.Errorf("invalid content format") } @@ -130,6 +131,7 @@ func (t *TokenPonyModel) ChatWithMessages(ctx context.Context, modelName string, return &ChatResponse{ Answer: &content, ReasonContent: &reasonContent, + ToolCalls: toolCalls, }, nil } @@ -181,6 +183,7 @@ func (t *TokenPonyModel) ChatStreamlyWithSender(ctx context.Context, modelName s } sawTerminal := false + accumulatedToolCalls := make(map[int]map[string]any) done, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error { if apiErr, ok := event["error"]; ok { return fmt.Errorf("tokenpony: upstream stream error: %v", apiErr) @@ -196,6 +199,7 @@ func (t *TokenPonyModel) ChatStreamlyWithSender(ctx context.Context, modelName s } if delta, ok := firstChoice["delta"].(map[string]interface{}); ok { + accumulateToolCallDeltas(delta, accumulatedToolCalls) if r, ok := delta["reasoning_content"].(string); ok && r != "" { rr := r if err := sender(nil, &rr); err != nil { @@ -217,6 +221,7 @@ func (t *TokenPonyModel) ChatStreamlyWithSender(ctx context.Context, modelName s if err != nil { return fmt.Errorf("failed to scan response body: %w", err) } + setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls) if !done && !sawTerminal { return fmt.Errorf("tokenpony: stream ended before [DONE] or finish_reason") } diff --git a/internal/entity/models/upstage.go b/internal/entity/models/upstage.go index 9533923144..7044c744e9 100644 --- a/internal/entity/models/upstage.go +++ b/internal/entity/models/upstage.go @@ -127,7 +127,8 @@ func (u *UpstageModel) ChatWithMessages(ctx context.Context, modelName string, m } content, ok := messageMap["content"].(string) - if !ok { + toolCalls := extractToolCalls(messageMap) + if !ok && len(toolCalls) == 0 { return nil, fmt.Errorf("invalid content format") } @@ -139,6 +140,7 @@ func (u *UpstageModel) ChatWithMessages(ctx context.Context, modelName string, m return &ChatResponse{ Answer: &content, ReasonContent: &reasonContent, + ToolCalls: toolCalls, }, nil } @@ -199,6 +201,7 @@ func (u *UpstageModel) ChatStreamlyWithSender(ctx context.Context, modelName str return fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body)) } sawTerminal := false + accumulatedToolCalls := make(map[int]map[string]any) done, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error { choices, ok := event["choices"].([]interface{}) if !ok || len(choices) == 0 { @@ -215,6 +218,8 @@ func (u *UpstageModel) ChatStreamlyWithSender(ctx context.Context, modelName str return nil } + accumulateToolCallDeltas(delta, accumulatedToolCalls) + if r, ok := delta["reasoning"].(string); ok && r != "" { if err := sender(nil, &r); err != nil { return err @@ -241,6 +246,8 @@ func (u *UpstageModel) ChatStreamlyWithSender(ctx context.Context, modelName str return fmt.Errorf("upstage: stream ended before [DONE] or finish_reason") } + setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls) + endOfStream := "[DONE]" if err := sender(&endOfStream, nil); err != nil { return err diff --git a/internal/entity/models/vllm.go b/internal/entity/models/vllm.go index 0389945048..efd81366a6 100644 --- a/internal/entity/models/vllm.go +++ b/internal/entity/models/vllm.go @@ -146,7 +146,8 @@ func (v *VllmModel) ChatWithMessages(ctx context.Context, modelName string, mess } content, ok := messageMap["content"].(string) - if !ok { + toolCalls := extractToolCalls(messageMap) + if !ok && len(toolCalls) == 0 { return nil, fmt.Errorf("invalid content format") } @@ -164,6 +165,7 @@ func (v *VllmModel) ChatWithMessages(ctx context.Context, modelName string, mess chatResponse := &ChatResponse{ Answer: &content, ReasonContent: &reasonContent, + ToolCalls: toolCalls, } return chatResponse, nil @@ -234,6 +236,7 @@ func (v *VllmModel) ChatStreamlyWithSender(ctx context.Context, modelName string } // SSE parsing: read line by line + accumulatedToolCalls := make(map[int]map[string]any) if _, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error { common.Info(fmt.Sprintf("%v", event)) @@ -252,6 +255,8 @@ func (v *VllmModel) ChatStreamlyWithSender(ctx context.Context, modelName string return nil } + accumulateToolCallDeltas(delta, accumulatedToolCalls) + reasoningContent, ok := delta["reasoning_content"].(string) if ok && reasoningContent != "" { if err := sender(nil, &reasoningContent); err != nil { @@ -270,6 +275,7 @@ func (v *VllmModel) ChatStreamlyWithSender(ctx context.Context, modelName string }); err != nil { return fmt.Errorf("failed to scan response body: %w", err) } + setSortedToolCallsResult(modelConfig, accumulatedToolCalls) // Send [DONE] marker for OpenAI compatibility endOfStream := "[DONE]" diff --git a/internal/entity/models/xai.go b/internal/entity/models/xai.go index 2022023b41..d0057b3476 100644 --- a/internal/entity/models/xai.go +++ b/internal/entity/models/xai.go @@ -133,7 +133,8 @@ func (x *XAIModel) ChatWithMessages(ctx context.Context, modelName string, messa } content, ok := messageMap["content"].(string) - if !ok { + toolCalls := extractToolCalls(messageMap) + if !ok && len(toolCalls) == 0 { return nil, fmt.Errorf("invalid content format") } @@ -150,6 +151,7 @@ func (x *XAIModel) ChatWithMessages(ctx context.Context, modelName string, messa chatResponse := &ChatResponse{ Answer: &content, ReasonContent: &reasonContent, + ToolCalls: toolCalls, } return chatResponse, nil @@ -201,6 +203,7 @@ func (x *XAIModel) ChatStreamlyWithSender(ctx context.Context, modelName string, } sawTerminal := false + accumulatedToolCalls := make(map[int]map[string]any) done, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error { choices, ok := event["choices"].([]interface{}) if !ok || len(choices) == 0 { @@ -217,6 +220,8 @@ func (x *XAIModel) ChatStreamlyWithSender(ctx context.Context, modelName string, return nil } + accumulateToolCallDeltas(delta, accumulatedToolCalls) + reasoningContent, ok := delta["reasoning_content"].(string) if ok && reasoningContent != "" { if err := sender(nil, &reasoningContent); err != nil { @@ -240,6 +245,7 @@ func (x *XAIModel) ChatStreamlyWithSender(ctx context.Context, modelName string, if err != nil { return fmt.Errorf("failed to scan response body: %w", err) } + setSortedToolCallsResult(chatModelConfig, accumulatedToolCalls) if !done && !sawTerminal { return fmt.Errorf("xai: stream ended before [DONE] or finish_reason") } diff --git a/internal/entity/models/xunfei.go b/internal/entity/models/xunfei.go index d401731961..79caafcb28 100644 --- a/internal/entity/models/xunfei.go +++ b/internal/entity/models/xunfei.go @@ -127,7 +127,8 @@ func (x *XunFeiModel) ChatWithMessages(ctx context.Context, modelName string, me } content, ok := messageMap["content"].(string) - if !ok { + toolCalls := extractToolCalls(messageMap) + if !ok && len(toolCalls) == 0 { return nil, fmt.Errorf("no message in response") } @@ -145,6 +146,7 @@ func (x *XunFeiModel) ChatWithMessages(ctx context.Context, modelName string, me chatResponse := &ChatResponse{ Answer: &content, ReasonContent: &reasonContent, + ToolCalls: toolCalls, } return chatResponse, nil @@ -209,6 +211,7 @@ func (x *XunFeiModel) ChatStreamlyWithSender(ctx context.Context, modelName stri } // SSE parsing: read line by line + accumulatedToolCalls := make(map[int]map[string]any) if _, err := ParseSSEStream[map[string]interface{}](resp.Body, func(event map[string]interface{}) error { if data, marshalErr := json.Marshal(event); marshalErr == nil { common.Info(string(data)) @@ -229,6 +232,8 @@ func (x *XunFeiModel) ChatStreamlyWithSender(ctx context.Context, modelName stri return nil } + accumulateToolCallDeltas(delta, accumulatedToolCalls) + reasoningContent, ok := delta["reasoning_content"].(string) if ok && reasoningContent != "" { if err := sender(nil, &reasoningContent); err != nil { @@ -247,6 +252,7 @@ func (x *XunFeiModel) ChatStreamlyWithSender(ctx context.Context, modelName stri }); err != nil { return fmt.Errorf("failed to scan response body: %w", err) } + setSortedToolCallsResult(modelConfig, accumulatedToolCalls) // Send [DONE] marker for OpenAI compatibility endOfStream := "[DONE]"