diff --git a/ai-provider/model-runtime/model-providers/authropic/parameter_names.json b/ai-provider/model-runtime/model-providers/authropic/parameter_names.json new file mode 100644 index 00000000..3e89a0c8 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/authropic/parameter_names.json @@ -0,0 +1,7 @@ +{ + "temperature": "temperature", + "top_p": "top_p", + "top_k": "", + "max_tokens": "max_tokens", + "response_format": "response_format" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/authropic/parameter_types.json b/ai-provider/model-runtime/model-providers/authropic/parameter_types.json new file mode 100644 index 00000000..2b9254ff --- /dev/null +++ b/ai-provider/model-runtime/model-providers/authropic/parameter_types.json @@ -0,0 +1,7 @@ +{ + "temperature": "float", + "top_p": "float", + "top_k": "int", + "max_tokens": "int", + "response_format": "" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/baichuan/parameter_names.json b/ai-provider/model-runtime/model-providers/baichuan/parameter_names.json new file mode 100644 index 00000000..dfe17109 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/baichuan/parameter_names.json @@ -0,0 +1,10 @@ +{ + "temperature": "temperature", + "top_p": "top_p", + "top_k": "", + "max_tokens": "max_tokens", + "with_search_enhance": "", + "presence_penalty": "presence_penalty", + "frequency_penalty": "frequency_penalty", + "res_format": "" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/baichuan/parameter_types.json b/ai-provider/model-runtime/model-providers/baichuan/parameter_types.json new file mode 100644 index 00000000..5b7eb3ae --- /dev/null +++ b/ai-provider/model-runtime/model-providers/baichuan/parameter_types.json @@ -0,0 +1,10 @@ +{ + "temperature": "float", + "top_p": "float", + "top_k": "int", + "max_tokens": "int", + "with_search_enhance": "boolean", + "presence_penalty": "float", + "frequency_penalty": "float", + "res_format": "string" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/bedrock/parameter_names.json b/ai-provider/model-runtime/model-providers/bedrock/parameter_names.json new file mode 100644 index 00000000..2105810a --- /dev/null +++ b/ai-provider/model-runtime/model-providers/bedrock/parameter_names.json @@ -0,0 +1,17 @@ +{ + "max_tokens": "max_tokens", + "temperature": "temperature", + "top_p": "top_p", + "top_k": "top_k", + "response_format": "response_format", + "max_gen_len": "max_tokens", + "max_new_tokens": "max_tokens", + "p": "top_p", + "k": "", + "presence_penalty": "presence_penalty", + "frequency_penalty": "frequency_penalty", + "topP": "top_p", + "maxTokens": "max_tokens", + "count_penalty": "", + "maxTokenCount": "max_tokens" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/bedrock/parameter_types.json b/ai-provider/model-runtime/model-providers/bedrock/parameter_types.json new file mode 100644 index 00000000..10fde4bf --- /dev/null +++ b/ai-provider/model-runtime/model-providers/bedrock/parameter_types.json @@ -0,0 +1,17 @@ +{ + "max_tokens": "int", + "temperature": "float", + "top_p": "float", + "top_k": "int", + "response_format": "", + "max_gen_len": "int", + "max_new_tokens": "int", + "p": "float", + "k": "int", + "presence_penalty": "float", + "frequency_penalty": "float", + "topP": "float", + "maxTokens": "int", + "count_penalty": "float", + "maxTokenCount": "int" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/chatglm/parameter_names.json b/ai-provider/model-runtime/model-providers/chatglm/parameter_names.json new file mode 100644 index 00000000..04e48155 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/chatglm/parameter_names.json @@ -0,0 +1,5 @@ +{ + "temperature": "temperature", + "top_p": "top_p", + "max_tokens": "max_tokens" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/chatglm/parameter_types.json b/ai-provider/model-runtime/model-providers/chatglm/parameter_types.json new file mode 100644 index 00000000..ce77622e --- /dev/null +++ b/ai-provider/model-runtime/model-providers/chatglm/parameter_types.json @@ -0,0 +1,5 @@ +{ + "temperature": "float", + "top_p": "float", + "max_tokens": "int" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/cohere/parameter_names.json b/ai-provider/model-runtime/model-providers/cohere/parameter_names.json new file mode 100644 index 00000000..51b8a9b0 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/cohere/parameter_names.json @@ -0,0 +1,10 @@ +{ + "temperature": "temperature", + "p": "top_p", + "k": "", + "presence_penalty": "presence_penalty", + "frequency_penalty": "frequency_penalty", + "max_tokens": "max_tokens", + "preamble_override": "", + "prompt_truncation": "" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/cohere/parameter_types.json b/ai-provider/model-runtime/model-providers/cohere/parameter_types.json new file mode 100644 index 00000000..07d29f03 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/cohere/parameter_types.json @@ -0,0 +1,10 @@ +{ + "temperature": "float", + "p": "float", + "k": "int", + "presence_penalty": "float", + "frequency_penalty": "float", + "max_tokens": "int", + "preamble_override": "string", + "prompt_truncation": "string" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/deepseek/parameter_names.json b/ai-provider/model-runtime/model-providers/deepseek/parameter_names.json new file mode 100644 index 00000000..0b6013f6 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/deepseek/parameter_names.json @@ -0,0 +1,9 @@ +{ + "temperature": "temperature", + "top_p": "top_p", + "max_tokens": "max_tokens", + "logprobs": "", + "top_logprobs": "", + "frequency_penalty": "frequency_penalty", + "response_format": "" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/deepseek/parameter_types.json b/ai-provider/model-runtime/model-providers/deepseek/parameter_types.json new file mode 100644 index 00000000..a1747c9f --- /dev/null +++ b/ai-provider/model-runtime/model-providers/deepseek/parameter_types.json @@ -0,0 +1,9 @@ +{ + "temperature": "float", + "top_p": "float", + "max_tokens": "int", + "logprobs": "boolean", + "top_logprobs": "int", + "frequency_penalty": "float", + "response_format": "string" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/fakegpt/parameter_names.json b/ai-provider/model-runtime/model-providers/fakegpt/parameter_names.json new file mode 100644 index 00000000..72761eff --- /dev/null +++ b/ai-provider/model-runtime/model-providers/fakegpt/parameter_names.json @@ -0,0 +1,6 @@ +{ + "max_tokens": "max_tokens", + "temperature": "temperature", + "top_p": "", + "top_k": "" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/fakegpt/parameter_types.json b/ai-provider/model-runtime/model-providers/fakegpt/parameter_types.json new file mode 100644 index 00000000..cdad8b93 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/fakegpt/parameter_types.json @@ -0,0 +1,6 @@ +{ + "max_tokens": "int", + "temperature": "float", + "top_p": "float", + "top_k": "int" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/fireworks/parameter_names.json b/ai-provider/model-runtime/model-providers/fireworks/parameter_names.json new file mode 100644 index 00000000..22b47a3d --- /dev/null +++ b/ai-provider/model-runtime/model-providers/fireworks/parameter_names.json @@ -0,0 +1,8 @@ +{ + "temperature": "temperature", + "top_p": "top_p", + "top_k": "", + "max_tokens": "max_tokens", + "context_length_exceeded_behavior": "", + "response_format": "response_format" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/fireworks/parameter_types.json b/ai-provider/model-runtime/model-providers/fireworks/parameter_types.json new file mode 100644 index 00000000..68634e4d --- /dev/null +++ b/ai-provider/model-runtime/model-providers/fireworks/parameter_types.json @@ -0,0 +1,8 @@ +{ + "temperature": "float", + "top_p": "float", + "top_k": "int", + "max_tokens": "int", + "context_length_exceeded_behavior": "string", + "response_format": "" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/google/parameter_names.json b/ai-provider/model-runtime/model-providers/google/parameter_names.json new file mode 100644 index 00000000..504ade4c --- /dev/null +++ b/ai-provider/model-runtime/model-providers/google/parameter_names.json @@ -0,0 +1,9 @@ +{ + "temperature": "temperature", + "top_p": "top_p", + "top_k": "", + "max_output_tokens": "max_tokens", + "json_schema": "json_schema", + "max_tokens_to_sample": "max_tokens", + "response_format": "response_format" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/google/parameter_types.json b/ai-provider/model-runtime/model-providers/google/parameter_types.json new file mode 100644 index 00000000..69f23893 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/google/parameter_types.json @@ -0,0 +1,9 @@ +{ + "temperature": "float", + "top_p": "float", + "top_k": "int", + "max_output_tokens": "int", + "json_schema": "", + "max_tokens_to_sample": "int", + "response_format": "" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/groq/parameter_names.json b/ai-provider/model-runtime/model-providers/groq/parameter_names.json new file mode 100644 index 00000000..31885729 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/groq/parameter_names.json @@ -0,0 +1,6 @@ +{ + "temperature": "temperature", + "top_p": "top_p", + "max_tokens": "max_tokens", + "response_format": "" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/groq/parameter_types.json b/ai-provider/model-runtime/model-providers/groq/parameter_types.json new file mode 100644 index 00000000..e1d6d4f7 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/groq/parameter_types.json @@ -0,0 +1,6 @@ +{ + "temperature": "float", + "top_p": "float", + "max_tokens": "int", + "response_format": "string" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/hunyuan/parameter_names.json b/ai-provider/model-runtime/model-providers/hunyuan/parameter_names.json new file mode 100644 index 00000000..6748dc89 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/hunyuan/parameter_names.json @@ -0,0 +1,6 @@ +{ + "temperature": "temperature", + "top_p": "top_p", + "max_tokens": "max_tokens", + "enable_enhance": "" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/hunyuan/parameter_types.json b/ai-provider/model-runtime/model-providers/hunyuan/parameter_types.json new file mode 100644 index 00000000..2bfd7a00 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/hunyuan/parameter_types.json @@ -0,0 +1,6 @@ +{ + "temperature": "float", + "top_p": "float", + "max_tokens": "int", + "enable_enhance": "boolean" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/minimax/parameter_names.json b/ai-provider/model-runtime/model-providers/minimax/parameter_names.json new file mode 100644 index 00000000..0a01d6d9 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/minimax/parameter_names.json @@ -0,0 +1,9 @@ +{ + "temperature": "temperature", + "top_p": "top_p", + "max_tokens": "max_tokens", + "mask_sensitive_info": "", + "presence_penalty": "presence_penalty", + "frequency_penalty": "frequency_penalty", + "plugin_web_search": "" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/minimax/parameter_types.json b/ai-provider/model-runtime/model-providers/minimax/parameter_types.json new file mode 100644 index 00000000..fab81b76 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/minimax/parameter_types.json @@ -0,0 +1,9 @@ +{ + "temperature": "float", + "top_p": "float", + "max_tokens": "int", + "mask_sensitive_info": "boolean", + "presence_penalty": "float", + "frequency_penalty": "float", + "plugin_web_search": "boolean" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/mistralai/parameter_names.json b/ai-provider/model-runtime/model-providers/mistralai/parameter_names.json new file mode 100644 index 00000000..1e633afa --- /dev/null +++ b/ai-provider/model-runtime/model-providers/mistralai/parameter_names.json @@ -0,0 +1,7 @@ +{ + "temperature": "temperature", + "top_p": "top_p", + "max_tokens": "max_tokens", + "safe_prompt": "", + "random_seed": "" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/mistralai/parameter_types.json b/ai-provider/model-runtime/model-providers/mistralai/parameter_types.json new file mode 100644 index 00000000..d6af8986 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/mistralai/parameter_types.json @@ -0,0 +1,7 @@ +{ + "temperature": "float", + "top_p": "float", + "max_tokens": "int", + "safe_prompt": "boolean", + "random_seed": "int" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/moonshot/parameter_names.json b/ai-provider/model-runtime/model-providers/moonshot/parameter_names.json new file mode 100644 index 00000000..31885729 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/moonshot/parameter_names.json @@ -0,0 +1,6 @@ +{ + "temperature": "temperature", + "top_p": "top_p", + "max_tokens": "max_tokens", + "response_format": "" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/moonshot/parameter_types.json b/ai-provider/model-runtime/model-providers/moonshot/parameter_types.json new file mode 100644 index 00000000..e1d6d4f7 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/moonshot/parameter_types.json @@ -0,0 +1,6 @@ +{ + "temperature": "float", + "top_p": "float", + "max_tokens": "int", + "response_format": "string" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/novita/parameter_names.json b/ai-provider/model-runtime/model-providers/novita/parameter_names.json new file mode 100644 index 00000000..8c525f08 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/novita/parameter_names.json @@ -0,0 +1,7 @@ +{ + "temperature": "temperature", + "top_p": "top_p", + "max_tokens": "max_tokens", + "frequency_penalty": "frequency_penalty", + "presence_penalty": "presence_penalty" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/novita/parameter_types.json b/ai-provider/model-runtime/model-providers/novita/parameter_types.json new file mode 100644 index 00000000..7e57631e --- /dev/null +++ b/ai-provider/model-runtime/model-providers/novita/parameter_types.json @@ -0,0 +1,7 @@ +{ + "temperature": "float", + "top_p": "float", + "max_tokens": "int", + "frequency_penalty": "float", + "presence_penalty": "float" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/nvidia/parameter_names.json b/ai-provider/model-runtime/model-providers/nvidia/parameter_names.json new file mode 100644 index 00000000..657401b1 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/nvidia/parameter_names.json @@ -0,0 +1,9 @@ +{ + "temperature": "temperature", + "top_p": "top_p", + "max_tokens": "max_tokens", + "frequency_penalty": "frequency_penalty", + "presence_penalty": "presence_penalty", + "random_seed": "", + "frequency_penalt": "frequency_penalty" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/nvidia/parameter_types.json b/ai-provider/model-runtime/model-providers/nvidia/parameter_types.json new file mode 100644 index 00000000..86076b47 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/nvidia/parameter_types.json @@ -0,0 +1,9 @@ +{ + "temperature": "float", + "top_p": "float", + "max_tokens": "int", + "frequency_penalty": "float", + "presence_penalty": "float", + "random_seed": "int", + "frequency_penalt": "float" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/openAI/parameter_names.json b/ai-provider/model-runtime/model-providers/openAI/parameter_names.json new file mode 100644 index 00000000..119668b7 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/openAI/parameter_names.json @@ -0,0 +1,11 @@ +{ + "max_tokens": "max_tokens", + "reasoning_effort": "", + "response_format": "response_format", + "temperature": "temperature", + "top_p": "top_p", + "presence_penalty": "presence_penalty", + "frequency_penalty": "frequency_penalty", + "seed": "", + "json_schema": "json_schema" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/openAI/parameter_types.json b/ai-provider/model-runtime/model-providers/openAI/parameter_types.json new file mode 100644 index 00000000..08eaee4f --- /dev/null +++ b/ai-provider/model-runtime/model-providers/openAI/parameter_types.json @@ -0,0 +1,11 @@ +{ + "max_tokens": "int", + "reasoning_effort": "string", + "response_format": "string", + "temperature": "float", + "top_p": "float", + "presence_penalty": "float", + "frequency_penalty": "float", + "seed": "int", + "json_schema": "" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/openrouter/parameter_names.json b/ai-provider/model-runtime/model-providers/openrouter/parameter_names.json new file mode 100644 index 00000000..61641188 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/openrouter/parameter_names.json @@ -0,0 +1,11 @@ +{ + "temperature": "temperature", + "top_p": "top_p", + "max_tokens": "max_tokens", + "top_k": "", + "context_length_exceeded_behavior": "", + "response_format": "response_format", + "presence_penalty": "presence_penalty", + "frequency_penalty": "frequency_penalty", + "seed": "" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/openrouter/parameter_types.json b/ai-provider/model-runtime/model-providers/openrouter/parameter_types.json new file mode 100644 index 00000000..9203bd5f --- /dev/null +++ b/ai-provider/model-runtime/model-providers/openrouter/parameter_types.json @@ -0,0 +1,11 @@ +{ + "temperature": "float", + "top_p": "float", + "max_tokens": "int", + "top_k": "int", + "context_length_exceeded_behavior": "string", + "response_format": "string", + "presence_penalty": "float", + "frequency_penalty": "float", + "seed": "int" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/perfxcloud/parameter_names.json b/ai-provider/model-runtime/model-providers/perfxcloud/parameter_names.json new file mode 100644 index 00000000..686218cb --- /dev/null +++ b/ai-provider/model-runtime/model-providers/perfxcloud/parameter_names.json @@ -0,0 +1,7 @@ +{ + "temperature": "temperature", + "max_tokens": "max_tokens", + "top_p": "top_p", + "top_k": "", + "repetition_penalty": "" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/perfxcloud/parameter_types.json b/ai-provider/model-runtime/model-providers/perfxcloud/parameter_types.json new file mode 100644 index 00000000..63d7e0d5 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/perfxcloud/parameter_types.json @@ -0,0 +1,7 @@ +{ + "temperature": "float", + "max_tokens": "int", + "top_p": "float", + "top_k": "int", + "repetition_penalty": "float" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/siliconflow/parameter_names.json b/ai-provider/model-runtime/model-providers/siliconflow/parameter_names.json new file mode 100644 index 00000000..3539e48d --- /dev/null +++ b/ai-provider/model-runtime/model-providers/siliconflow/parameter_names.json @@ -0,0 +1,10 @@ +{ + "max_tokens": "max_tokens", + "temperature": "temperature", + "top_p": "top_p", + "frequency_penalty": "frequency_penalty", + "top_k": "", + "response_format": "", + "seed": "", + "repetition_penalty": "" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/siliconflow/parameter_types.json b/ai-provider/model-runtime/model-providers/siliconflow/parameter_types.json new file mode 100644 index 00000000..911a0e89 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/siliconflow/parameter_types.json @@ -0,0 +1,10 @@ +{ + "max_tokens": "int", + "temperature": "float", + "top_p": "float", + "frequency_penalty": "float", + "top_k": "int", + "response_format": "string", + "seed": "int", + "repetition_penalty": "float" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/spark/parameter_names.json b/ai-provider/model-runtime/model-providers/spark/parameter_names.json new file mode 100644 index 00000000..7ef0d51b --- /dev/null +++ b/ai-provider/model-runtime/model-providers/spark/parameter_names.json @@ -0,0 +1,6 @@ +{ + "temperature": "temperature", + "max_tokens": "max_tokens", + "top_k": "", + "show_ref_label": "" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/spark/parameter_types.json b/ai-provider/model-runtime/model-providers/spark/parameter_types.json new file mode 100644 index 00000000..39c3530d --- /dev/null +++ b/ai-provider/model-runtime/model-providers/spark/parameter_types.json @@ -0,0 +1,6 @@ +{ + "temperature": "float", + "max_tokens": "int", + "top_k": "int", + "show_ref_label": "boolean" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/stepfun/parameter_names.json b/ai-provider/model-runtime/model-providers/stepfun/parameter_names.json new file mode 100644 index 00000000..04e48155 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/stepfun/parameter_names.json @@ -0,0 +1,5 @@ +{ + "temperature": "temperature", + "top_p": "top_p", + "max_tokens": "max_tokens" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/stepfun/parameter_types.json b/ai-provider/model-runtime/model-providers/stepfun/parameter_types.json new file mode 100644 index 00000000..ce77622e --- /dev/null +++ b/ai-provider/model-runtime/model-providers/stepfun/parameter_types.json @@ -0,0 +1,5 @@ +{ + "temperature": "float", + "top_p": "float", + "max_tokens": "int" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/tongyi/parameter_names.json b/ai-provider/model-runtime/model-providers/tongyi/parameter_names.json new file mode 100644 index 00000000..1ab534f8 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/tongyi/parameter_names.json @@ -0,0 +1,11 @@ +{ + "temperature": "temperature", + "max_tokens": "max_tokens", + "top_p": "top_p", + "top_k": "", + "seed": "", + "repetition_penalty": "", + "response_format": "response_format", + "enable_search": "", + "frequency_penalty": "frequency_penalty" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/tongyi/parameter_types.json b/ai-provider/model-runtime/model-providers/tongyi/parameter_types.json new file mode 100644 index 00000000..f7bc905f --- /dev/null +++ b/ai-provider/model-runtime/model-providers/tongyi/parameter_types.json @@ -0,0 +1,11 @@ +{ + "temperature": "float", + "max_tokens": "int", + "top_p": "float", + "top_k": "int", + "seed": "int", + "repetition_penalty": "float", + "response_format": "string", + "enable_search": "boolean", + "frequency_penalty": "float" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/upstage/parameter_names.json b/ai-provider/model-runtime/model-providers/upstage/parameter_names.json new file mode 100644 index 00000000..b90b9732 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/upstage/parameter_names.json @@ -0,0 +1,6 @@ +{ + "temperature": "temperature", + "top_p": "top_p", + "max_tokens": "max_tokens", + "seed": "" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/upstage/parameter_types.json b/ai-provider/model-runtime/model-providers/upstage/parameter_types.json new file mode 100644 index 00000000..43d711bd --- /dev/null +++ b/ai-provider/model-runtime/model-providers/upstage/parameter_types.json @@ -0,0 +1,6 @@ +{ + "temperature": "float", + "top_p": "float", + "max_tokens": "int", + "seed": "int" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/vertex_ai/parameter_names.json b/ai-provider/model-runtime/model-providers/vertex_ai/parameter_names.json new file mode 100644 index 00000000..e69e9579 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/vertex_ai/parameter_names.json @@ -0,0 +1,10 @@ +{ + "temperature": "temperature", + "top_p": "top_p", + "top_k": "", + "max_output_tokens": "max_tokens", + "json_schema": "json_schema", + "presence_penalty": "presence_penalty", + "frequency_penalty": "frequency_penalty", + "max_tokens": "max_tokens" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/vertex_ai/parameter_types.json b/ai-provider/model-runtime/model-providers/vertex_ai/parameter_types.json new file mode 100644 index 00000000..39fa7e23 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/vertex_ai/parameter_types.json @@ -0,0 +1,10 @@ +{ + "temperature": "float", + "top_p": "float", + "top_k": "int", + "max_output_tokens": "int", + "json_schema": "", + "presence_penalty": "float", + "frequency_penalty": "float", + "max_tokens": "int" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/wenxin/parameter_names.json b/ai-provider/model-runtime/model-providers/wenxin/parameter_names.json new file mode 100644 index 00000000..755a3897 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/wenxin/parameter_names.json @@ -0,0 +1,11 @@ +{ + "temperature": "temperature", + "top_p": "top_p", + "max_tokens": "max_tokens", + "presence_penalty": "presence_penalty", + "frequency_penalty": "frequency_penalty", + "response_format": "response_format", + "disable_search": "", + "min_output_tokens": "max_tokens", + "max_output_tokens": "max_tokens" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/wenxin/parameter_types.json b/ai-provider/model-runtime/model-providers/wenxin/parameter_types.json new file mode 100644 index 00000000..c6046da1 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/wenxin/parameter_types.json @@ -0,0 +1,11 @@ +{ + "temperature": "float", + "top_p": "float", + "max_tokens": "int", + "presence_penalty": "float", + "frequency_penalty": "float", + "response_format": "", + "disable_search": "boolean", + "min_output_tokens": "int", + "max_output_tokens": "int" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/yi/parameter_names.json b/ai-provider/model-runtime/model-providers/yi/parameter_names.json new file mode 100644 index 00000000..673a990c --- /dev/null +++ b/ai-provider/model-runtime/model-providers/yi/parameter_names.json @@ -0,0 +1,5 @@ +{ + "temperature": "temperature", + "max_tokens": "max_tokens", + "top_p": "top_p" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/yi/parameter_types.json b/ai-provider/model-runtime/model-providers/yi/parameter_types.json new file mode 100644 index 00000000..3a4b2715 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/yi/parameter_types.json @@ -0,0 +1,5 @@ +{ + "temperature": "float", + "max_tokens": "int", + "top_p": "float" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/zhinao/parameter_names.json b/ai-provider/model-runtime/model-providers/zhinao/parameter_names.json new file mode 100644 index 00000000..8c525f08 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/zhinao/parameter_names.json @@ -0,0 +1,7 @@ +{ + "temperature": "temperature", + "top_p": "top_p", + "max_tokens": "max_tokens", + "frequency_penalty": "frequency_penalty", + "presence_penalty": "presence_penalty" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/zhinao/parameter_types.json b/ai-provider/model-runtime/model-providers/zhinao/parameter_types.json new file mode 100644 index 00000000..7e57631e --- /dev/null +++ b/ai-provider/model-runtime/model-providers/zhinao/parameter_types.json @@ -0,0 +1,7 @@ +{ + "temperature": "float", + "top_p": "float", + "max_tokens": "int", + "frequency_penalty": "float", + "presence_penalty": "float" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/zhipuai/parameter_names.json b/ai-provider/model-runtime/model-providers/zhipuai/parameter_names.json new file mode 100644 index 00000000..37c7e348 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/zhipuai/parameter_names.json @@ -0,0 +1,10 @@ +{ + "temperature": "temperature", + "top_p": "top_p", + "do_sample": "", + "max_tokens": "max_tokens", + "web_search": "", + "response_format": "", + "stream": "", + "return_type": "" +} \ No newline at end of file diff --git a/ai-provider/model-runtime/model-providers/zhipuai/parameter_types.json b/ai-provider/model-runtime/model-providers/zhipuai/parameter_types.json new file mode 100644 index 00000000..bd3a7615 --- /dev/null +++ b/ai-provider/model-runtime/model-providers/zhipuai/parameter_types.json @@ -0,0 +1,10 @@ +{ + "temperature": "float", + "top_p": "float", + "do_sample": "boolean", + "max_tokens": "int", + "web_search": "boolean", + "response_format": "string", + "stream": "boolean", + "return_type": "string" +} \ No newline at end of file diff --git a/all_parameter_names.json b/all_parameter_names.json new file mode 100644 index 00000000..bd10c2c7 --- /dev/null +++ b/all_parameter_names.json @@ -0,0 +1,243 @@ +{ + "moonshot": { + "temperature": "temperature", + "top_p": "top_p", + "max_tokens": "max_tokens", + "response_format": "" + }, + "cohere": { + "temperature": "temperature", + "p": "top_p", + "k": "", + "presence_penalty": "presence_penalty", + "frequency_penalty": "frequency_penalty", + "max_tokens": "max_tokens", + "preamble_override": "", + "prompt_truncation": "" + }, + "zhipuai": { + "temperature": "temperature", + "top_p": "top_p", + "do_sample": "", + "max_tokens": "max_tokens", + "web_search": "", + "response_format": "", + "stream": "", + "return_type": "" + }, + "chatglm": { + "temperature": "temperature", + "top_p": "top_p", + "max_tokens": "max_tokens" + }, + "mistralai": { + "temperature": "temperature", + "top_p": "top_p", + "max_tokens": "max_tokens", + "safe_prompt": "", + "random_seed": "" + }, + "openrouter": { + "temperature": "temperature", + "top_p": "top_p", + "max_tokens": "max_tokens", + "top_k": "", + "context_length_exceeded_behavior": "", + "response_format": "response_format", + "presence_penalty": "presence_penalty", + "frequency_penalty": "frequency_penalty", + "seed": "" + }, + "siliconflow": { + "max_tokens": "max_tokens", + "temperature": "temperature", + "top_p": "top_p", + "frequency_penalty": "frequency_penalty", + "top_k": "", + "response_format": "", + "seed": "", + "repetition_penalty": "" + }, + "google": { + "temperature": "temperature", + "top_p": "top_p", + "top_k": "", + "max_output_tokens": "max_tokens", + "json_schema": "json_schema", + "max_tokens_to_sample": "max_tokens", + "response_format": "response_format" + }, + "fireworks": { + "temperature": "temperature", + "top_p": "top_p", + "top_k": "", + "max_tokens": "max_tokens", + "context_length_exceeded_behavior": "", + "response_format": "response_format" + }, + "authropic": { + "temperature": "temperature", + "top_p": "top_p", + "top_k": "", + "max_tokens": "max_tokens", + "response_format": "response_format" + }, + "groq": { + "temperature": "temperature", + "top_p": "top_p", + "max_tokens": "max_tokens", + "response_format": "" + }, + "novita": { + "temperature": "temperature", + "top_p": "top_p", + "max_tokens": "max_tokens", + "frequency_penalty": "frequency_penalty", + "presence_penalty": "presence_penalty" + }, + "wenxin": { + "temperature": "temperature", + "top_p": "top_p", + "max_tokens": "max_tokens", + "presence_penalty": "presence_penalty", + "frequency_penalty": "frequency_penalty", + "response_format": "response_format", + "disable_search": "", + "min_output_tokens": "max_tokens", + "max_output_tokens": "max_tokens" + }, + "vertex_ai": { + "temperature": "temperature", + "top_p": "top_p", + "top_k": "", + "max_output_tokens": "max_tokens", + "json_schema": "json_schema", + "presence_penalty": "presence_penalty", + "frequency_penalty": "frequency_penalty", + "max_tokens": "max_tokens" + }, + "fakegpt": { + "max_tokens": "max_tokens", + "temperature": "temperature", + "top_p": "", + "top_k": "" + }, + "minimax": { + "temperature": "temperature", + "top_p": "top_p", + "max_tokens": "max_tokens", + "mask_sensitive_info": "", + "presence_penalty": "presence_penalty", + "frequency_penalty": "frequency_penalty", + "plugin_web_search": "" + }, + "deepseek": { + "temperature": "temperature", + "top_p": "top_p", + "max_tokens": "max_tokens", + "logprobs": "", + "top_logprobs": "", + "frequency_penalty": "frequency_penalty", + "response_format": "" + }, + "bedrock": { + "max_tokens": "max_tokens", + "temperature": "temperature", + "top_p": "top_p", + "top_k": "top_k", + "response_format": "response_format", + "max_gen_len": "max_tokens", + "max_new_tokens": "max_tokens", + "p": "top_p", + "k": "", + "presence_penalty": "presence_penalty", + "frequency_penalty": "frequency_penalty", + "topP": "top_p", + "maxTokens": "max_tokens", + "count_penalty": "", + "maxTokenCount": "max_tokens" + }, + "zhinao": { + "temperature": "temperature", + "top_p": "top_p", + "max_tokens": "max_tokens", + "frequency_penalty": "frequency_penalty", + "presence_penalty": "presence_penalty" + }, + "stepfun": { + "temperature": "temperature", + "top_p": "top_p", + "max_tokens": "max_tokens" + }, + "hunyuan": { + "temperature": "temperature", + "top_p": "top_p", + "max_tokens": "max_tokens", + "enable_enhance": "" + }, + "yi": { + "temperature": "temperature", + "max_tokens": "max_tokens", + "top_p": "top_p" + }, + "baichuan": { + "temperature": "temperature", + "top_p": "top_p", + "top_k": "", + "max_tokens": "max_tokens", + "with_search_enhance": "", + "presence_penalty": "presence_penalty", + "frequency_penalty": "frequency_penalty", + "res_format": "" + }, + "nvidia": { + "temperature": "temperature", + "top_p": "top_p", + "max_tokens": "max_tokens", + "frequency_penalty": "frequency_penalty", + "presence_penalty": "presence_penalty", + "random_seed": "", + "frequency_penalt": "frequency_penalty" + }, + "upstage": { + "temperature": "temperature", + "top_p": "top_p", + "max_tokens": "max_tokens", + "seed": "" + }, + "perfxcloud": { + "temperature": "temperature", + "max_tokens": "max_tokens", + "top_p": "top_p", + "top_k": "", + "repetition_penalty": "" + }, + "tongyi": { + "temperature": "temperature", + "max_tokens": "max_tokens", + "top_p": "top_p", + "top_k": "", + "seed": "", + "repetition_penalty": "", + "response_format": "response_format", + "enable_search": "", + "frequency_penalty": "frequency_penalty" + }, + "spark": { + "temperature": "temperature", + "max_tokens": "max_tokens", + "top_k": "", + "show_ref_label": "" + }, + "openAI": { + "max_tokens": "max_tokens", + "reasoning_effort": "", + "response_format": "response_format", + "temperature": "temperature", + "top_p": "top_p", + "presence_penalty": "presence_penalty", + "frequency_penalty": "frequency_penalty", + "seed": "", + "json_schema": "json_schema" + } +} \ No newline at end of file diff --git a/controller/mcp/iml.go b/controller/mcp/iml.go new file mode 100644 index 00000000..70362675 --- /dev/null +++ b/controller/mcp/iml.go @@ -0,0 +1,72 @@ +package mcp + +import ( + mcp_server "github.com/APIParkLab/APIPark/mcp-server" + "github.com/APIParkLab/APIPark/module/mcp" + "github.com/APIParkLab/APIPark/module/system" + "github.com/eolinker/go-common/utils" + "github.com/gin-gonic/gin" + mcp2 "github.com/mark3labs/mcp-go/mcp" + "github.com/mark3labs/mcp-go/server" +) + +var _ IMcpController = (*imlMcpController)(nil) + +type imlMcpController struct { + settingModule system.ISettingModule `autowired:""` + mcpModule mcp.IMcpModule `autowired:""` + server *server.SSEServer +} + +func (i *imlMcpController) OnComplete() { + s := server.NewMCPServer("APIPark MCP Server", "1.0.0", server.WithLogging()) + s.AddTool( + mcp2.NewTool( + "apipark_service_list", + mcp2.WithDescription("This tool is a standardized interface provided by the Apipark platform under the MCP (Model Context Protocol) framework, designed to retrieve metadata for all registered services in bulk. By invoking this tool, users can efficiently explore the complete list of published services and their core attributes, serving as a prerequisite for subsequent actions such as querying detailed API lists via service IDs, requesting access permissions, or integrating services."), + mcp2.WithString("keyword", mcp2.Description("Keyword for fuzzy search")), + ), + i.mcpModule.Services, + ) + s.AddTool( + mcp2.NewTool( + "apipark_service_api_list", + mcp2.WithDescription("This tool is a standardized MCP (Model Context Protocol) interface provided by the Apipark platform, designed to retrieve OpenAPI specification documents for all APIs under a specified service using its service ID. By invoking this tool, users gain precise access to detailed API definitions (including endpoints, parameters, request/response schemas) for debugging, integration, or client SDK generation."), + mcp2.WithString("service", mcp2.Description("Service ID")), + ), + i.mcpModule.APIs, + ) + s.AddTool( + mcp2.NewTool( + "apipark_subscriber_authorization_list", + mcp2.WithDescription("This tool is a standardized MCP (Model Context Protocol) interface provided by the Apipark platform, designed to retrieve authorization credentials for subscribers who have access to specific services. By invoking this tool, users obtain critical authentication metadata (e.g., API keys, OAuth tokens) required to securely invoke APIs via apipark_invoke_api, ensuring compliant and permission-bound integrations."), + mcp2.WithString("service", mcp2.Description("Service ID"), mcp2.Required()), + ), + i.mcpModule.SubscriberAuthorizations) + s.AddTool( + mcp2.NewTool( + "apipark_invoke_api", + mcp2.WithDescription("This tool is a core MCP (Model Context Protocol) interface provided by the Apipark platform, enabling users to programmatically invoke APIs using metadata from apipark_service_api_list (API schemas) and credentials from apipark_subscriber_authorization_list. It acts as a unified gateway for executing API requests with built-in authentication, parameter validation, and error handling, returning structured responses for integration workflows."), + mcp2.WithString("path", mcp2.Description("API path"), mcp2.Required()), + mcp2.WithString("method", mcp2.Description("API method"), mcp2.Required()), + mcp2.WithString("content-type", mcp2.Description("API Request Content-Type. If method is POST,PUT,PATCH, it must be set. If not set, it will be ignored.")), + mcp2.WithObject("query", mcp2.Description("API Request query,param type is map[string]string")), + mcp2.WithObject("header", mcp2.Description("API Request header,param type is map[string]string")), + mcp2.WithString("body", mcp2.Description("API Request body")), + ), + i.mcpModule.Invoke, + ) + i.server = server.NewSSEServer(s, server.WithBasePath(mcp_server.GlobalBasePath)) +} + +func (i *imlMcpController) GlobalMCPHandle(ctx *gin.Context) { + cfg := i.settingModule.Get(ctx) + req := ctx.Request.WithContext(utils.SetGatewayInvoke(ctx.Request.Context(), cfg.InvokeAddress)) + i.server.ServeHTTP(ctx.Writer, req) +} + +func (i *imlMcpController) MCPHandle(ctx *gin.Context) { + cfg := i.settingModule.Get(ctx) + req := ctx.Request.WithContext(utils.SetGatewayInvoke(ctx.Request.Context(), cfg.InvokeAddress)) + mcp_server.ServeHTTP(ctx.Writer, req) +} diff --git a/controller/mcp/mcp.go b/controller/mcp/mcp.go new file mode 100644 index 00000000..0386204f --- /dev/null +++ b/controller/mcp/mcp.go @@ -0,0 +1,19 @@ +package mcp + +import ( + "reflect" + + "github.com/eolinker/go-common/autowire" + "github.com/gin-gonic/gin" +) + +type IMcpController interface { + MCPHandle(ctx *gin.Context) + GlobalMCPHandle(ctx *gin.Context) +} + +func init() { + autowire.Auto[IMcpController](func() reflect.Value { + return reflect.ValueOf(new(imlMcpController)) + }) +} diff --git a/controller/system-apikey/apikey.go b/controller/system-apikey/apikey.go new file mode 100644 index 00000000..f108a4e7 --- /dev/null +++ b/controller/system-apikey/apikey.go @@ -0,0 +1,24 @@ +package system_apikey + +import ( + "reflect" + + system_apikey_dto "github.com/APIParkLab/APIPark/module/system-apikey/dto" + "github.com/eolinker/go-common/autowire" + "github.com/gin-gonic/gin" +) + +type IAPIKeyController interface { + Create(ctx *gin.Context, input *system_apikey_dto.Create) error + Update(ctx *gin.Context, id string, input *system_apikey_dto.Update) error + Delete(ctx *gin.Context, id string) error + Get(ctx *gin.Context, id string) (*system_apikey_dto.APIKey, error) + Search(ctx *gin.Context, keyword string) ([]*system_apikey_dto.Item, error) + SimpleList(ctx *gin.Context) ([]*system_apikey_dto.SimpleItem, error) +} + +func init() { + autowire.Auto[IAPIKeyController](func() reflect.Value { + return reflect.ValueOf(new(imlAPIKeyController)) + }) +} diff --git a/controller/system-apikey/iml.go b/controller/system-apikey/iml.go new file mode 100644 index 00000000..895c95d8 --- /dev/null +++ b/controller/system-apikey/iml.go @@ -0,0 +1,37 @@ +package system_apikey + +import ( + system_apikey "github.com/APIParkLab/APIPark/module/system-apikey" + system_apikey_dto "github.com/APIParkLab/APIPark/module/system-apikey/dto" + "github.com/gin-gonic/gin" +) + +var _ IAPIKeyController = new(imlAPIKeyController) + +type imlAPIKeyController struct { + apikeyModule system_apikey.IAPIKeyModule `autowired:""` +} + +func (i *imlAPIKeyController) Create(ctx *gin.Context, input *system_apikey_dto.Create) error { + return i.apikeyModule.Create(ctx, input) +} + +func (i *imlAPIKeyController) Update(ctx *gin.Context, id string, input *system_apikey_dto.Update) error { + return i.apikeyModule.Update(ctx, id, input) +} + +func (i *imlAPIKeyController) Delete(ctx *gin.Context, id string) error { + return i.apikeyModule.Delete(ctx, id) +} + +func (i *imlAPIKeyController) Get(ctx *gin.Context, id string) (*system_apikey_dto.APIKey, error) { + return i.apikeyModule.Get(ctx, id) +} + +func (i *imlAPIKeyController) Search(ctx *gin.Context, keyword string) ([]*system_apikey_dto.Item, error) { + return i.apikeyModule.Search(ctx, keyword) +} + +func (i *imlAPIKeyController) SimpleList(ctx *gin.Context) ([]*system_apikey_dto.SimpleItem, error) { + return i.apikeyModule.SimpleList(ctx) +} diff --git a/extract_parameters.py b/extract_parameters.py new file mode 100644 index 00000000..18993707 --- /dev/null +++ b/extract_parameters.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +import os +import yaml +import json +from pathlib import Path + +# 基础路径 +base_path = "/Users/liujian/work/golang/src/github.com/APIParkLab/APIPark/ai-provider/model-runtime/model-providers" + +# 遍历所有供应商目录 +for provider_dir in os.listdir(base_path): + provider_path = os.path.join(base_path, provider_dir) + + # 检查是否是目录 + if not os.path.isdir(provider_path): + continue + + # 检查llm目录是否存在 + llm_path = os.path.join(provider_path, "llm") + if not os.path.isdir(llm_path): + continue + + # 收集参数名和模板 + param_names = {} + + # 遍历llm目录下的所有yaml文件 + for yaml_file in os.listdir(llm_path): + if not yaml_file.endswith(('.yaml', '.yml')): + continue + + yaml_path = os.path.join(llm_path, yaml_file) + + try: + # 读取yaml文件 + with open(yaml_path, 'r', encoding='utf-8') as f: + data = yaml.safe_load(f) + + # 提取parameter_rules中的name和use_template值 + if data and 'parameter_rules' in data: + for param in data['parameter_rules']: + if 'name' in param: + name = param['name'] + # 获取use_template值,如果不存在则为空字符串 + template = param.get('use_template', '') + + # 如果参数名不在字典中或者当前模板不为空 + if name not in param_names or (template and not param_names[name]): + param_names[name] = template + except Exception as e: + print(f"处理 {yaml_path} 时出错: {e}") + + # 将参数名和模板保存为JSON + if param_names: + output_file = os.path.join(provider_path, "parameter_names.json") + with open(output_file, 'w', encoding='utf-8') as f: + json.dump(param_names, f, indent=2, ensure_ascii=False) + + print(f"已为 {provider_dir} 提取 {len(param_names)} 个唯一参数") \ No newline at end of file diff --git a/generate_all_unique_params.py b/generate_all_unique_params.py new file mode 100644 index 00000000..9efce22d --- /dev/null +++ b/generate_all_unique_params.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +import json + +# 读取现有的合并文件 +input_file = "/Users/liujian/work/golang/src/github.com/APIParkLab/APIPark/all_parameter_names.json" +with open(input_file, 'r', encoding='utf-8') as f: + all_params = json.load(f) + +# 收集所有唯一的参数名 +unique_params = {} + +# 遍历所有供应商的参数 +for provider, params in all_params.items(): + for param_name, param_value in params.items(): + if param_name not in unique_params: + unique_params[param_name] = param_value + +# 按字母顺序排序参数 +sorted_params = {k: unique_params[k] for k in sorted(unique_params.keys())} + +# 保存合并后的文件 +output_file = "/Users/liujian/work/golang/src/github.com/APIParkLab/APIPark/unique_parameters.json" +with open(output_file, 'w', encoding='utf-8') as f: + json.dump(sorted_params, f, indent=2, ensure_ascii=False) + +print(f"生成了包含所有唯一参数的文件: {output_file}") \ No newline at end of file diff --git a/go.mod b/go.mod index d7878223..0073ca66 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/go-sql-driver/mysql v1.7.0 github.com/google/uuid v1.6.0 github.com/influxdata/influxdb-client-go/v2 v2.14.0 + github.com/mark3labs/mcp-go v0.17.0 github.com/nsqio/go-nsq v1.1.0 github.com/ollama/ollama v0.5.8 github.com/urfave/cli v1.22.16 @@ -63,6 +64,7 @@ require ( github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect + github.com/yosida95/uritemplate/v3 v3.0.2 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.13 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect @@ -84,4 +86,4 @@ require ( //replace github.com/eolinker/ap-account => ../../eolinker/ap-account // -//replace github.com/eolinker/go-common => ../../eolinker/go-common +replace github.com/eolinker/go-common => ../../eolinker/go-common diff --git a/go.sum b/go.sum index 0322d23f..c0fa85a9 100644 --- a/go.sum +++ b/go.sum @@ -101,6 +101,8 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mark3labs/mcp-go v0.17.0 h1:5Ps6T7qXr7De/2QTqs9h6BKeZ/qdeUeGrgM5lPzi930= +github.com/mark3labs/mcp-go v0.17.0/go.mod h1:KmJndYv7GIgcPVwEKJjNcbhVQ+hJGJhrCCB/9xITzpE= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -149,6 +151,8 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/urfave/cli v1.22.16 h1:MH0k6uJxdwdeWQTwhSO42Pwr4YLrNLwBtg1MRgTqPdQ= github.com/urfave/cli v1.22.16/go.mod h1:EeJR6BKodywf4zciqrdw6hpCPk68JO9z5LazXZMn5Po= +github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= +github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= go.etcd.io/etcd/client/pkg/v3 v3.5.13 h1:RVZSAnWWWiI5IrYAXjQorajncORbS0zI48LQlE2kQWg= go.etcd.io/etcd/client/pkg/v3 v3.5.13/go.mod h1:XxHT4u1qU12E2+po+UVPrEeL94Um6zL58ppuJWXSAB8= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= diff --git a/mcp-server/openapi.go b/mcp-server/openapi.go new file mode 100644 index 00000000..b3a1c922 --- /dev/null +++ b/mcp-server/openapi.go @@ -0,0 +1,152 @@ +package mcp_server + +import ( + "encoding/json" + "net/http" + + "github.com/getkin/kin-openapi/openapi3" +) + +var ( + openapi3Loader = openapi3.NewLoader() +) + +type MCPInfo struct { + Name string + Description string + Apis []*API +} + +type API struct { + Path string `json:"path"` + Method string `json:"method"` + ContentType string `json:"content_type"` + Summary string `json:"summary"` + Description string `json:"description"` + Params []*openapi3.Parameter `json:"params"` + Body map[string]interface{} `json:"body"` +} + +func ConvertMCPFromOpenAPI3Data(data []byte) (*MCPInfo, error) { + spec, err := openapi3Loader.LoadFromData(data) + if err != nil { + return nil, err + } + return parseOpenAPI3(spec) +} + +func parseOpenAPI3(spec *openapi3.T) (*MCPInfo, error) { + items := spec.Paths.Map() + apis := make([]*API, 0, len(items)*4) + for path, item := range items { + pathApis, err := genAPIs(path, item) + if err != nil { + return nil, err + } + apis = append(apis, pathApis...) + } + + return &MCPInfo{ + Name: spec.Info.Title, + Description: spec.Info.Description, + Apis: apis, + }, nil +} + +var ( + validMethods = []string{ + http.MethodGet, + http.MethodPost, + http.MethodPut, + http.MethodPatch, + http.MethodDelete, + http.MethodHead, + http.MethodOptions, + } +) + +func genAPIs(path string, item *openapi3.PathItem) ([]*API, error) { + apis := make([]*API, 0, 8) + for _, method := range validMethods { + opt := item.GetOperation(method) + if opt == nil { + continue + } + api, err := genAPI(method, path, opt, item.Parameters) + if err != nil { + return nil, err + } + apis = append(apis, api) + } + return apis, nil +} + +func genAPI(method string, path string, opt *openapi3.Operation, params openapi3.Parameters) (*API, error) { + api := &API{ + Method: method, + Path: path, + Summary: opt.Summary, + Description: opt.Description, + Params: make([]*openapi3.Parameter, 0, len(params)+len(opt.Parameters)), + } + if api.Summary == "" { + api.Summary = opt.Description + } + parameters := make([]*openapi3.ParameterRef, 0, len(params)+len(opt.Parameters)) + parameters = append(parameters, opt.Parameters...) + parameters = append(parameters, params...) + for _, param := range parameters { + if param.Value != nil { + api.Params = append(api.Params, param.Value) + } + } + if opt.RequestBody != nil && opt.RequestBody.Value != nil && opt.RequestBody.Value.Content != nil { + for mediaType, media := range opt.RequestBody.Value.Content { + if media != nil && media.Schema != nil { + api.ContentType = mediaType + body, err := recurseSchemaRef(media.Schema) + if err != nil { + return nil, err + } + api.Body = body + } + } + } + + return api, nil +} + +func recurseSchemaRef(ref *openapi3.SchemaRef) (map[string]interface{}, error) { + if ref == nil || ref.Value == nil { + return nil, nil + } + data, err := json.Marshal(ref.Value) + if err != nil { + return nil, err + } + m := make(map[string]interface{}) + err = json.Unmarshal(data, &m) + if err != nil { + return nil, err + } + + if ref.Value.Properties != nil { + m["properties"] = make(map[string]interface{}) + for k, v := range ref.Value.Properties { + v, err := recurseSchemaRef(v) + if err != nil { + return nil, err + } + m["properties"].(map[string]interface{})[k] = v + } + } + if ref.Value.Items != nil { + v, err := recurseSchemaRef(ref.Value.Items) + if err != nil { + return nil, err + } + m["items"] = v + } + + return m, nil +} diff --git a/mcp-server/server.go b/mcp-server/server.go new file mode 100644 index 00000000..3ad5dc65 --- /dev/null +++ b/mcp-server/server.go @@ -0,0 +1,71 @@ +package mcp_server + +import ( + "fmt" + "net/http" + "strings" + + "github.com/mark3labs/mcp-go/server" + + "github.com/eolinker/eosc" +) + +var ( + mcpServer = NewServer() + ServiceBasePath = "/api/v1/mcp/service" + GlobalBasePath = "/openapi/v1/mcp/global" +) + +func NewServer() *Server { + return &Server{ + sseServers: eosc.BuildUntyped[string, *server.SSEServer](), + } +} + +type Server struct { + sseServers eosc.Untyped[string, *server.SSEServer] +} + +func (s *Server) Set(path string, sseServer *server.SSEServer) { + s.sseServers.Set(path, sseServer) +} + +func (s *Server) Del(path string) { + s.sseServers.Del(path) +} + +func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + serviceId := getServiceId(r.URL.Path) + sseServer, has := s.sseServers.Get(serviceId) + if has { + sseServer.ServeHTTP(w, r) + return + } + http.NotFound(w, r) + return +} + +func getServiceId(path string) string { + id := strings.TrimPrefix(path, ServiceBasePath) + id = strings.Trim(id, "/") + id = strings.TrimSuffix(id, "/message") + id = strings.TrimSuffix(id, "/sse") + return id +} + +func SetSSEServer(sid string, name string, version string, tools ...ITool) { + s := server.NewMCPServer(name, version) + for _, tool := range tools { + tool.RegisterMCP(s) + } + sseServer := server.NewSSEServer(s, server.WithBasePath(fmt.Sprintf("%s/%s", ServiceBasePath, sid))) + mcpServer.Set(sid, sseServer) +} + +func DelSSEServer(sid string) { + mcpServer.Del(sid) +} + +func ServeHTTP(w http.ResponseWriter, r *http.Request) { + mcpServer.ServeHTTP(w, r) +} diff --git a/mcp-server/tool.go b/mcp-server/tool.go new file mode 100644 index 00000000..db6cae00 --- /dev/null +++ b/mcp-server/tool.go @@ -0,0 +1,111 @@ +package mcp_server + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + + "github.com/eolinker/go-common/utils" + + "github.com/mark3labs/mcp-go/mcp" + "github.com/mark3labs/mcp-go/server" +) + +type ITool interface { + RegisterMCP(s *server.MCPServer) +} + +type Tool struct { + name string + url string + method string + contentType string + opts []mcp.ToolOption +} + +func NewTool(name string, uri string, method string, contentType string, opts ...mcp.ToolOption) ITool { + return &Tool{ + name: name, + url: uri, + method: method, + contentType: contentType, + opts: opts, + } +} + +func (t *Tool) RegisterMCP(s *server.MCPServer) { + s.AddTool(mcp.NewTool(t.name, t.opts...), func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + invokeAddress := utils.GatewayInvoke(ctx) + if invokeAddress == "" { + return nil, fmt.Errorf("invoke address is empty") + } + u, err := url.Parse(invokeAddress) + if err != nil { + return nil, fmt.Errorf("invalid invoke address %s", invokeAddress) + } + if u.Scheme == "" { + u.Scheme = "http" + } + + path := t.url + queries := url.Values{} + headers := make(map[string]string) + body := "" + for k, v := range request.Params.Arguments { + if k == "body" { + tmp, _ := json.Marshal(v) + body = string(tmp) + continue + } + sps := strings.SplitN(k, "#", 2) + if len(sps) != 2 { + return nil, fmt.Errorf("invalid key %s", k) + } + switch sps[0] { + case "query": + queries.Set(sps[1], v.(string)) + case "header": + headers[sps[1]] = v.(string) + case "path": + p, ok := v.(string) + if !ok { + return nil, fmt.Errorf("invalid path %s", v) + } + path = strings.Replace(path, fmt.Sprintf("{%s}", sps[1]), p, -1) + } + } + u.Path = path + u.RawQuery = queries.Encode() + + req, err := http.NewRequest(t.method, u.String(), strings.NewReader(body)) + if err != nil { + return nil, err + } + for k, v := range headers { + req.Header.Set(k, v) + } + if t.contentType != "" { + req.Header.Set("Content-Type", t.contentType) + } + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + d, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("status code %d, %s", resp.StatusCode, string(d)) + } + + return mcp.NewToolResultText(string(d)), nil + }) +} + +var client = http.Client{} diff --git a/merge_parameter_names.py b/merge_parameter_names.py new file mode 100644 index 00000000..618f46b9 --- /dev/null +++ b/merge_parameter_names.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +import os +import json +from pathlib import Path + +# 基础路径 +base_path = "/Users/liujian/work/golang/src/github.com/APIParkLab/APIPark/ai-provider/model-runtime/model-providers" + +# 合并后的结果 +merged_params = {} + +# 遍历所有供应商目录 +for provider_dir in os.listdir(base_path): + provider_path = os.path.join(base_path, provider_dir) + + # 检查是否是目录 + if not os.path.isdir(provider_path): + continue + + # 检查parameter_names.json文件是否存在 + param_file = os.path.join(provider_path, "parameter_names.json") + if not os.path.isfile(param_file): + continue + + try: + # 读取parameter_names.json文件 + with open(param_file, 'r', encoding='utf-8') as f: + params = json.load(f) + + # 将供应商的参数添加到合并结果中 + merged_params[provider_dir] = params + + print(f"Added parameters from {provider_dir}") + except Exception as e: + print(f"Error processing {param_file}: {e}") + +# 保存合并后的文件 +output_file = "/Users/liujian/work/golang/src/github.com/APIParkLab/APIPark/all_parameter_names.json" +with open(output_file, 'w', encoding='utf-8') as f: + json.dump(merged_params, f, indent=2, ensure_ascii=False) + +print(f"Merged parameters saved to {output_file}") \ No newline at end of file diff --git a/module/ai-api/schema.go b/module/ai-api/schema.go index fc69b83c..5429182a 100644 --- a/module/ai-api/schema.go +++ b/module/ai-api/schema.go @@ -35,43 +35,6 @@ func genOperation(summary string, description string, variables []*ai_api_dto.Ai return operation } -func genRequestHeaders() openapi3.Parameters { - return openapi3.Parameters{ - { - Value: &openapi3.Parameter{ - Name: "Authorization", - In: "header", - Description: "your_apipark_apikey", // 替换Prompt的变量列表 - Required: true, - Example: "your_apipark_apikey", - }, - }, - } -} - -func genRequestParameters(variables []*ai_api_dto.AiPromptVariable) openapi3.Parameters { - return openapi3.Parameters{ - { - Value: &openapi3.Parameter{ - Name: "variables", - In: "body", - Description: "Replace the variable list of Prompt", // 替换Prompt的变量列表 - Schema: genVariableSchema(variables).NewRef(), - Required: true, - }, - }, - { - Value: &openapi3.Parameter{ - Name: "messages", - In: "body", - Description: "Chat Message", - Schema: messagesSchemaRef, - Required: true, - }, - }, - } -} - func genRequestBody(variables []*ai_api_dto.AiPromptVariable) *openapi3.RequestBodyRef { requestBody := openapi3.NewRequestBody() requestBody.Content = openapi3.NewContentWithSchema(genRequestBodySchema(variables), []string{"application/json"}) @@ -96,6 +59,10 @@ func genRequestBodySchema(variables []*ai_api_dto.AiPromptVariable) *openapi3.Sc result.WithProperty("variables", genVariableSchema(variables)) result.WithRequired([]string{"variables", "messages"}) } + streamSchema := openapi3.NewBoolSchema() + streamSchema.Title = "stream" + streamSchema.Description = "Whether to stream the response" + result.WithProperty("stream", streamSchema) result.WithPropertyRef("messages", messagesSchemaRef) diff --git a/module/catalogue/catalogue.go b/module/catalogue/catalogue.go index c665d681..ad41e5e8 100644 --- a/module/catalogue/catalogue.go +++ b/module/catalogue/catalogue.go @@ -3,6 +3,7 @@ package catalogue import ( "context" "reflect" + "time" "github.com/APIParkLab/APIPark/module/system" @@ -47,3 +48,9 @@ func init() { return reflect.ValueOf(catalogueModule) }) } + +func formatTimeByMinute(org int64) time.Time { + t := time.Unix(org, 0) + location, _ := time.LoadLocation("Asia/Shanghai") + return time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), 0, 0, location) +} diff --git a/module/catalogue/dto/output.go b/module/catalogue/dto/output.go index ef84220b..615504d1 100644 --- a/module/catalogue/dto/output.go +++ b/module/catalogue/dto/output.go @@ -18,15 +18,19 @@ type ServiceItem struct { Logo string `json:"logo"` ApiNum int64 `json:"api_num"` SubscriberNum int64 `json:"subscriber_num"` + InvokeCount int64 `json:"invoke_count"` + EnableMCP bool `json:"enable_mcp"` + ServiceKind string `json:"service_kind"` } type ServiceDetail struct { - Name string `json:"name"` - Description string `json:"description"` - Document string `json:"document"` - Basic *ServiceBasic `json:"basic"` - //Apis []*ServiceApi `json:"apis"` - APIDoc string `json:"api_doc"` + Name string `json:"name"` + Description string `json:"description"` + Document string `json:"document"` + Basic *ServiceBasic `json:"basic"` + APIDoc string `json:"api_doc"` + MCPServerAddress string `json:"mcp_server_address"` + MCPAccessConfig string `json:"mcp_access_config"` } type ServiceBasic struct { @@ -42,6 +46,7 @@ type ServiceBasic struct { ServiceKind string `json:"service_kind"` InvokeAddress string `json:"invoke_address"` SitePrefix string `json:"site_prefix"` + EnableMCP bool `json:"enable_mcp"` } type ServiceApiBasic struct { diff --git a/module/catalogue/iml.go b/module/catalogue/iml.go index f09cf820..42a19fbb 100644 --- a/module/catalogue/iml.go +++ b/module/catalogue/iml.go @@ -6,6 +6,14 @@ import ( "fmt" "math" "sort" + "strings" + "time" + + mcp_server "github.com/APIParkLab/APIPark/mcp-server" + + "github.com/APIParkLab/APIPark/module/monitor/driver" + + "github.com/APIParkLab/APIPark/service/monitor" "github.com/APIParkLab/APIPark/service/setting" @@ -63,6 +71,7 @@ type imlCatalogueModule struct { transaction store.ITransaction `autowired:""` clusterService cluster.IClusterService `autowired:""` settingService setting.ISettingService `autowired:""` + monitorService monitor.IMonitorService `autowired:""` root *Root } @@ -124,6 +133,84 @@ func (i *imlCatalogueModule) ExportAll(ctx context.Context) ([]*catalogue_dto.Ex }), nil } +func (i *imlCatalogueModule) getExecutor(ctx context.Context, clusterId string) (driver.IExecutor, error) { + info, err := i.monitorService.GetByCluster(ctx, clusterId) + if err != nil { + return nil, err + } + return driver.CreateExecutor(info.Driver, info.Config) +} + +func (i *imlCatalogueModule) statistics(ctx context.Context, clusterId string, groupBy string, start, end time.Time, wheres []monitor.MonWhereItem, limit int) (map[string]monitor.MonCommonData, error) { + executor, err := i.getExecutor(ctx, clusterId) + if err != nil { + return nil, err + } + result, err := executor.CommonStatistics(ctx, start, end, groupBy, limit, wheres) + if err != nil { + return nil, err + } + return result, nil +} + +func (i *imlCatalogueModule) genCommonWheres(ctx context.Context, clusterIds ...string) ([]monitor.MonWhereItem, error) { + + clusters, err := i.clusterService.List(ctx, clusterIds...) + if err != nil { + return nil, err + } + clusterIds = utils.SliceToSlice(clusters, func(item *cluster.Cluster) string { + return item.Uuid + }) + + wheres := make([]monitor.MonWhereItem, 0, 1) + nodes, err := i.clusterService.Nodes(ctx, clusterIds...) + if err != nil { + return nil, err + } + nodeIds := utils.SliceToSlice(nodes, func(s *cluster.Node) string { + return s.Name + }) + wheres = append(wheres, monitor.MonWhereItem{ + Key: "node", + Operation: "in", + Values: nodeIds, + }) + + return wheres, nil +} + +func (i *imlCatalogueModule) ProviderStatistics(ctx context.Context, start, end time.Time, serviceIds ...string) (map[string]int64, error) { + clusterId := cluster.DefaultClusterID + _, err := i.clusterService.Get(ctx, clusterId) + if err != nil { + return nil, err + } + + wheres, err := i.genCommonWheres(ctx, clusterId) + if err != nil { + return nil, err + } + + if len(serviceIds) > 0 { + wheres = append(wheres, monitor.MonWhereItem{ + Key: "provider", + Operation: "in", + Values: serviceIds, + }) + } + statisticMap, err := i.statistics(ctx, clusterId, "provider", start, end, wheres, 0) + if err != nil { + return nil, err + } + resultMap := make(map[string]int64) + for key, item := range statisticMap { + resultMap[key] = item.RequestTotal + } + + return resultMap, nil +} + func (i *imlCatalogueModule) Subscribe(ctx context.Context, subscribeInfo *catalogue_dto.SubscribeService) error { if len(subscribeInfo.Applications) == 0 { return fmt.Errorf("applications is empty") @@ -226,9 +313,17 @@ func (i *imlCatalogueModule) Subscribe(ctx context.Context, subscribeInfo *catal } return nil }) - } +var mcpDefaultConfig = `{ + "mcpServers": { + "%s": { + "url": "%s" + } + } +} +` + func (i *imlCatalogueModule) ServiceDetail(ctx context.Context, sid string) (*catalogue_dto.ServiceDetail, error) { // 获取服务的基本信息 s, err := i.serviceService.Get(ctx, sid) @@ -290,7 +385,15 @@ func (i *imlCatalogueModule) ServiceDetail(ctx context.Context, sid string) (*ca } invokeAddress, _ := i.settingService.Get(ctx, setting.KeyInvokeAddress) sitePrefix, _ := i.settingService.Get(ctx, setting.KeySitePrefix) + mcpAccessAddress := "" + mcpAccessConfig := "" + if s.EnableMCP { + if sitePrefix != "" { + mcpAccessAddress = fmt.Sprintf("%s%s/%s/sse", strings.TrimSuffix(sitePrefix, "/"), mcp_server.ServiceBasePath, s.Id) + mcpAccessConfig = fmt.Sprintf(mcpDefaultConfig, s.Name, mcpAccessAddress) + } + } return &catalogue_dto.ServiceDetail{ Name: s.Name, Description: s.Description, @@ -308,8 +411,11 @@ func (i *imlCatalogueModule) ServiceDetail(ctx context.Context, sid string) (*ca ServiceKind: s.Kind.String(), InvokeAddress: invokeAddress, SitePrefix: sitePrefix, + EnableMCP: s.EnableMCP, }, - APIDoc: apiDoc, + APIDoc: apiDoc, + MCPServerAddress: mcpAccessAddress, + MCPAccessConfig: mcpAccessConfig, }, nil } @@ -348,16 +454,14 @@ func (i *imlCatalogueModule) Services(ctx context.Context, keyword string) ([]*c return nil, err } - //// 获取服务API数量 - //apiCountMap, err := i.apiDocService.LatestAPICountByServices(ctx, serviceIds...) - //if err != nil { - // return nil, err - //} - subscriberCountMap, err := i.subscribeService.CountMapByService(ctx, subscribe.ApplyStatusSubscribe, serviceIds...) if err != nil { return nil, err } + invokeStatisticMap, err := i.ProviderStatistics(ctx, time.Now().Add(-24*30*time.Hour), time.Now(), serviceIds...) + if err != nil { + return nil, err + } result := make([]*catalogue_dto.ServiceItem, 0, len(items)) for _, v := range items { @@ -375,6 +479,9 @@ func (i *imlCatalogueModule) Services(ctx context.Context, keyword string) ([]*c SubscriberNum: subscriberCountMap[v.Id], Description: v.Description, Logo: v.Logo, + EnableMCP: v.EnableMCP, + ServiceKind: v.Kind.String(), + InvokeCount: invokeStatisticMap[v.Id], }) } sort.Slice(result, func(i, j int) bool { diff --git a/module/mcp/dto/output.go b/module/mcp/dto/output.go new file mode 100644 index 00000000..78f7f13b --- /dev/null +++ b/module/mcp/dto/output.go @@ -0,0 +1,44 @@ +package mcp_dto + +import ( + "time" +) + +type Service struct { + Id string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + ServiceKind string `json:"service_kind"` + Apis []*API `json:"apis"` + CreateTime time.Time `json:"create_time"` + UpdateTime time.Time `json:"update_time"` +} + +type App struct { + Id string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + CreateTime time.Time `json:"create_time"` + UpdateTime time.Time `json:"update_time"` +} + +type API struct { + Name string `json:"name"` + Method string `json:"method"` + Path string `json:"path"` + Description string `json:"description"` +} + +type ServiceAPI struct { + ServiceID string `json:"service_id"` + ServiceName string `json:"service_name"` + APIDoc interface{} `json:"api_doc"` +} + +type AppAuthorization struct { + Id string `json:"id"` + Name string `json:"name"` + Position string `json:"position"` + TokenName string `json:"token_name"` + Config string `json:"config"` +} diff --git a/module/mcp/iml.go b/module/mcp/iml.go new file mode 100644 index 00000000..4fa3c26f --- /dev/null +++ b/module/mcp/iml.go @@ -0,0 +1,323 @@ +package mcp + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" + + "github.com/APIParkLab/APIPark/service/subscribe" + + "github.com/getkin/kin-openapi/openapi3" + + "gorm.io/gorm" + + "github.com/APIParkLab/APIPark/service/release" + + mcp_dto "github.com/APIParkLab/APIPark/module/mcp/dto" + "github.com/eolinker/go-common/utils" + + api_doc "github.com/APIParkLab/APIPark/service/api-doc" + + application_authorization "github.com/APIParkLab/APIPark/service/application-authorization" + + "github.com/APIParkLab/APIPark/service/service" + + "github.com/mark3labs/mcp-go/mcp" +) + +var _ IMcpModule = (*imlMcpModule)(nil) + +var ( + openapi3Loader = openapi3.NewLoader() +) + +type imlMcpModule struct { + serviceService service.IServiceService `autowired:""` + appService service.IServiceService `autowired:""` + appAuthorizationService application_authorization.IAuthorizationService `autowired:""` + apiDocService api_doc.IAPIDocService `autowired:""` + subscriberService subscribe.ISubscribeService `autowired:""` + releaseService release.IReleaseService `autowired:""` +} + +func (i *imlMcpModule) Services(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { + keyword, _ := req.Params.Arguments["keyword"].(string) + list, err := i.serviceService.Search(ctx, keyword, map[string]interface{}{ + "as_server": true, + }, "update_at desc") + if err != nil { + return nil, fmt.Errorf("search service error: %w", err) + } + if len(list) == 0 { + list, err = i.serviceService.Search(ctx, "", map[string]interface{}{ + "as_server": true, + }, "update_at desc") + if err != nil { + return nil, fmt.Errorf("search service error: %w", err) + } + } + result := make([]*mcp_dto.Service, 0, len(list)) + for _, s := range list { + serviceRelease, err := i.releaseService.GetRunning(ctx, s.Id) + if err != nil { + if !errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fmt.Errorf("get service release error: %w,service id is %s", err, s.Id) + } + continue + } + _, _, apiDocRelease, _, _, err := i.releaseService.GetReleaseInfos(ctx, serviceRelease.UUID) + if err != nil { + if !errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fmt.Errorf("get service release info error: %w,service id is %s", err, s.Id) + } + continue + } + commit, err := i.apiDocService.GetDocCommit(ctx, apiDocRelease.Commit) + if err != nil { + if !errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fmt.Errorf("get api doc release error: %w,service id is %s", err, s.Id) + } + continue + } + T, err := openapi3Loader.LoadFromData([]byte(commit.Data.Content)) + if err != nil { + return nil, fmt.Errorf("load openapi3 error: %w,service id is %s", err, s.Id) + } + apis := make([]*mcp_dto.API, 0, len(T.Paths.Map())) + for path, v := range T.Paths.Map() { + for method, opt := range v.Operations() { + apis = append(apis, &mcp_dto.API{ + Name: opt.Summary, + Method: method, + Path: path, + Description: opt.Description, + }) + } + } + result = append(result, &mcp_dto.Service{ + Id: s.Id, + Name: s.Name, + Description: s.Name, + ServiceKind: s.Kind.String(), + CreateTime: s.CreateTime, + UpdateTime: s.UpdateTime, + Apis: apis, + }) + } + data, _ := json.Marshal(result) + return mcp.NewToolResultText(string(data)), nil + +} + +func (i *imlMcpModule) Apps(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { + keyword := req.Params.Arguments["keyword"].(string) + condition := make(map[string]interface{}) + condition["as_app"] = true + list, err := i.serviceService.Search(ctx, keyword, condition, "update_at desc") + if err != nil { + return nil, fmt.Errorf("search service error: %w", err) + } + if len(list) == 0 { + list, err = i.serviceService.Search(ctx, "", condition, "update_at desc") + if err != nil { + return nil, fmt.Errorf("search service error: %w", err) + } + } + data, _ := json.Marshal(utils.SliceToSlice(list, func(s *service.Service) *mcp_dto.App { + return &mcp_dto.App{ + Id: s.Id, + Name: s.Name, + Description: s.Name, + CreateTime: s.CreateTime, + UpdateTime: s.UpdateTime, + } + })) + return mcp.NewToolResultText(string(data)), nil +} + +func (i *imlMcpModule) APIs(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { + serviceId, _ := req.Params.Arguments["service"].(string) + serviceIds := make([]string, 0, 1) + if serviceId == "" { + serviceIds = append(serviceIds, serviceId) + } + serviceList, err := i.serviceService.ServiceList(ctx) + if err != nil { + return nil, fmt.Errorf("get service list error: %w", err) + } + result := make([]*mcp_dto.ServiceAPI, 0, len(serviceList)) + + for _, s := range serviceList { + serviceRelease, err := i.releaseService.GetRunning(ctx, s.Id) + if err != nil { + if !errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fmt.Errorf("get service release error: %w,service id is %s", err, s.Id) + } + continue + } + _, _, apiDocRelease, _, _, err := i.releaseService.GetReleaseInfos(ctx, serviceRelease.UUID) + if err != nil { + if !errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fmt.Errorf("get service release info error: %w,service id is %s", err, s.Id) + } + continue + } + commit, err := i.apiDocService.GetDocCommit(ctx, apiDocRelease.Commit) + if err != nil { + if !errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fmt.Errorf("get api doc release error: %w,service id is %s", err, s.Id) + } + continue + } + T, err := openapi3Loader.LoadFromData([]byte(commit.Data.Content)) + if err != nil { + return nil, fmt.Errorf("load openapi3 error: %w,service id is %s", err, s.Id) + } + result = append(result, &mcp_dto.ServiceAPI{ + ServiceID: s.Id, + ServiceName: s.Name, + APIDoc: T, + }) + } + data, _ := json.Marshal(result) + return mcp.NewToolResultText(string(data)), nil +} + +func (i *imlMcpModule) SubscriberAuthorizations(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { + serviceId, ok := req.Params.Arguments["service"].(string) + if !ok { + return nil, fmt.Errorf("service id is required") + } + subscribes, err := i.subscriberService.Subscribers(ctx, serviceId, subscribe.ApplyStatusSubscribe) + if err != nil { + return nil, fmt.Errorf("get subscriber error: %w,service id is %s", err, serviceId) + } + appIds := utils.SliceToSlice(subscribes, func(s *subscribe.Subscribe) string { + return s.Application + }) + if len(appIds) == 0 { + return nil, fmt.Errorf("no subscriber found,service id is %s", serviceId) + } + list, err := i.appAuthorizationService.ListByApp(ctx, appIds...) + if err != nil { + return nil, fmt.Errorf("get app authorization error: %w,app ids is %s", err, appIds) + } + result := utils.SliceToSlice(list, func(a *application_authorization.Authorization) *mcp_dto.AppAuthorization { + return &mcp_dto.AppAuthorization{ + Id: a.UUID, + Name: a.Name, + Position: a.Position, + TokenName: a.TokenName, + Config: a.Config, + } + }, func(a *application_authorization.Authorization) bool { + if a.Type != "apikey" { + return false + } + if a.ExpireTime != 0 && a.ExpireTime < time.Now().Unix() { + return false + } + return true + }) + data, _ := json.Marshal(result) + return mcp.NewToolResultText(string(data)), nil +} + +var ( + client = &http.Client{} +) + +func (i *imlMcpModule) Invoke(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { + gatewayInvoke := utils.GatewayInvoke(ctx) + if gatewayInvoke == "" { + return nil, fmt.Errorf("gateway invoke is required") + } + u, err := url.Parse(gatewayInvoke) + if err != nil { + return nil, fmt.Errorf("parse gateway invoke error: %w", err) + } + if u.Scheme == "" { + u.Scheme = "http" + } + + path, ok := req.Params.Arguments["path"].(string) + if !ok { + return nil, fmt.Errorf("invalid path") + } + u.Path = fmt.Sprintf("%s/%s", strings.TrimSuffix(u.Path, "/"), strings.TrimPrefix(path, "/")) + + method, ok := req.Params.Arguments["method"].(string) + if !ok { + method = "GET" + } + queryParam := url.Values{} + query, ok := req.Params.Arguments["query"].(map[string]interface{}) + if ok { + for k, v := range query { + switch v := v.(type) { + case string: + queryParam.Add(k, v) + case []string: + for _, value := range v { + queryParam.Add(k, value) + } + default: + return nil, fmt.Errorf("invalid query param type: %T", v) + } + } + } + u.RawQuery = queryParam.Encode() + headerParam := http.Header{} + header, ok := req.Params.Arguments["header"].(map[string]interface{}) + if ok { + for k, v := range header { + switch v := v.(type) { + case string: + headerParam.Set(k, v) + case []string: + for _, value := range v { + headerParam.Set(k, value) + } + default: + return nil, fmt.Errorf("invalid header param type: %T", v) + } + } + } + + body, ok := req.Params.Arguments["body"].(string) + if !ok { + body = "" + } + + contentType, ok := req.Params.Arguments["content-type"].(string) + if !ok { + contentType = "application/json" + } + request, err := http.NewRequest(method, u.String(), strings.NewReader(body)) + if err != nil { + return nil, fmt.Errorf("new request error: %w", err) + } + request.Header = headerParam + request.Header.Set("Content-Type", contentType) + + resp, err := client.Do(request) + if err != nil { + return nil, fmt.Errorf("request error: %w", err) + } + defer resp.Body.Close() + + data, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("read response error: %w", err) + } + if resp.StatusCode != 200 { + return nil, fmt.Errorf("response error: %s", string(data)) + } + return mcp.NewToolResultText(string(data)), nil +} diff --git a/module/mcp/module.go b/module/mcp/module.go new file mode 100644 index 00000000..1549dc11 --- /dev/null +++ b/module/mcp/module.go @@ -0,0 +1,29 @@ +package mcp + +import ( + "context" + "reflect" + + "github.com/eolinker/go-common/autowire" + + "github.com/mark3labs/mcp-go/mcp" +) + +type IMcpModule interface { + // Services 获取服务列表 + Services(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) + // Apps 获取应用列表 + Apps(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) + // APIs 获取API列表 + APIs(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) + + // SubscriberAuthorizations 获取订阅者授权 + SubscriberAuthorizations(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) + Invoke(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) +} + +func init() { + autowire.Auto[IMcpModule](func() reflect.Value { + return reflect.ValueOf(new(imlMcpModule)) + }) +} diff --git a/module/service/dto/input.go b/module/service/dto/input.go index a33a7ee4..bdaa7bc5 100644 --- a/module/service/dto/input.go +++ b/module/service/dto/input.go @@ -24,6 +24,7 @@ type CreateService struct { AsApp *bool `json:"as_app"` AsServer *bool `json:"as_server"` ModelMapping string `json:"model_mapping"` + EnableMCP bool `json:"enable_mcp"` } type EditService struct { @@ -38,6 +39,7 @@ type EditService struct { ApprovalType *string `json:"approval_type"` State *string `json:"state"` ModelMapping *string `json:"model_mapping"` + EnableMCP *bool `json:"enable_mcp"` } type CreateApp struct { diff --git a/module/service/dto/output.go b/module/service/dto/output.go index 0bd258c3..35a80cbe 100644 --- a/module/service/dto/output.go +++ b/module/service/dto/output.go @@ -56,6 +56,7 @@ type ServiceItem struct { Provider *auto.Label `json:"provider,omitempty" aolabel:"ai_provider"` State string `json:"state"` CanDelete bool `json:"can_delete"` + EnableMCP bool `json:"enable_mcp"` } type AppItem struct { @@ -101,11 +102,12 @@ type Service struct { ProviderType string `json:"provider_type,omitempty"` Model string `json:"model,omitempty"` ApprovalType string `json:"approval_type"` - AsServer bool `json:"as_server"` - AsApp bool `json:"as_app"` ServiceKind string `json:"service_kind"` State string `json:"state"` ModelMapping string `json:"model_mapping"` + AsServer bool `json:"as_server"` + AsApp bool `json:"as_app"` + EnableMCP bool `json:"enable_mcp"` } type App struct { @@ -139,6 +141,7 @@ func ToService(model *service.Service) *Service { AsServer: model.AsServer, AsApp: model.AsApp, ServiceKind: model.Kind.String(), + EnableMCP: model.EnableMCP, } state := FromServiceState(model.State) if state == ServiceStateNormal { diff --git a/module/service/iml.go b/module/service/iml.go index 5da979b2..34e3ca27 100644 --- a/module/service/iml.go +++ b/module/service/iml.go @@ -9,6 +9,14 @@ import ( "strings" "time" + "github.com/eolinker/go-common/register" + + "github.com/mark3labs/mcp-go/mcp" + + mcp_server "github.com/APIParkLab/APIPark/mcp-server" + + "github.com/APIParkLab/APIPark/service/release" + "github.com/APIParkLab/APIPark/gateway" "github.com/APIParkLab/APIPark/service/cluster" @@ -17,9 +25,9 @@ import ( model_runtime "github.com/APIParkLab/APIPark/ai-provider/model-runtime" - "github.com/eolinker/eosc/log" - "github.com/APIParkLab/APIPark/resources/access" + "github.com/eolinker/eosc/log" + "github.com/eolinker/go-common/server" "github.com/eolinker/ap-account/service/role" @@ -76,9 +84,83 @@ type imlServiceModule struct { clusterService cluster.IClusterService `autowired:""` transaction store.ITransaction `autowired:""` + releaseService release.IReleaseService `autowired:""` serviceModelMappingService service_model_mapping.IServiceModelMappingService `autowired:""` } +func (i *imlServiceModule) OnInit() { + register.Handle(func(v server.Server) { + ctx := context.Background() + services, err := i.serviceService.ServiceList(ctx) + if err != nil { + log.Error(err) + return + } + for _, s := range services { + err = i.updateMCPServer(ctx, s.Id, s.Name, "1.0") + if err != nil { + log.Error(err) + return + } + } + }) +} + +func (i *imlServiceModule) updateMCPServer(ctx context.Context, sid string, name string, version string) error { + r, err := i.releaseService.GetRunning(ctx, sid) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil + } + return err + } + _, _, apiDocCommit, _, _, err := i.releaseService.GetReleaseInfos(ctx, r.UUID) + if err != nil { + return fmt.Errorf("get release info error: %w", err) + } + commitDoc, err := i.apiDocService.GetDocCommit(ctx, apiDocCommit.Commit) + if err != nil { + return fmt.Errorf("get api doc commit error: %w", err) + } + mcpInfo, err := mcp_server.ConvertMCPFromOpenAPI3Data([]byte(commitDoc.Data.Content)) + if err != nil { + return fmt.Errorf("convert mcp from openapi3 data error: %w", err) + } + tools := make([]mcp_server.ITool, 0, len(mcpInfo.Apis)) + for _, a := range mcpInfo.Apis { + toolOptions := make([]mcp.ToolOption, 0, len(a.Params)+2) + toolOptions = append(toolOptions, mcp.WithDescription(a.Description)) + for _, v := range a.Params { + vSchema, err := v.MarshalJSON() + if err != nil { + return fmt.Errorf("marshal param schema error: %w", err) + } + key := fmt.Sprintf("%s#%s", v.In, v.Name) + description := fmt.Sprintf("%s\nparam schema is %s", v.Description, string(vSchema)) + param := mcp.WithString(key, mcp.Description(description)) + if v.Required { + param = mcp.WithString(key, mcp.Description(description), mcp.Required()) + } + + toolOptions = append(toolOptions, param) + } + if a.Body != nil { + bodySchema, err := json.Marshal(a.Body) + if err != nil { + return fmt.Errorf("marshal body schema error: %w", err) + } + toolOptions = append(toolOptions, mcp.WithString("body", mcp.Description(fmt.Sprintf("body schema is %s", string(bodySchema))))) + } + tools = append(tools, mcp_server.NewTool(a.Summary, a.Path, a.Method, a.ContentType, toolOptions...)) + } + mcp_server.SetSSEServer(sid, name, version, tools...) + return nil +} + +func (i *imlServiceModule) deleteMCPServer(ctx context.Context, sid string) { + mcp_server.DelSSEServer(sid) +} + func (i *imlServiceModule) ExportAll(ctx context.Context) ([]*service_dto.ExportService, error) { services, err := i.serviceService.ServiceList(ctx) if err != nil { @@ -305,6 +387,7 @@ func toServiceItem(model *service.Service) *service_dto.ServiceItem { CreateTime: auto.TimeLabel(model.CreateTime), UpdateTime: auto.TimeLabel(model.UpdateTime), Team: auto.UUID(model.Team), + EnableMCP: model.EnableMCP, ServiceKind: model.Kind.String(), } state := service_dto.FromServiceState(model.State) @@ -351,6 +434,7 @@ func (i *imlServiceModule) Create(ctx context.Context, teamID string, input *ser ApprovalType: service.ApprovalType(input.ApprovalType), AdditionalConfig: make(map[string]string), Kind: service.Kind(input.Kind), + EnableMCP: input.EnableMCP, } if mo.ServiceType == service.PublicService && mo.Catalogue == "" { return nil, fmt.Errorf("catalogue can not be empty") @@ -425,6 +509,14 @@ func (i *imlServiceModule) Create(ctx context.Context, teamID string, input *ser return err } } + if input.EnableMCP { + err = i.updateMCPServer(ctx, input.Id, input.Name, "1.0") + if err != nil { + return err + } + } else { + i.deleteMCPServer(ctx, input.Id) + } return nil }) if err != nil { @@ -467,6 +559,7 @@ func (i *imlServiceModule) Edit(ctx context.Context, id string, input *service_d Catalogue: input.Catalogue, AdditionalConfig: &info.AdditionalConfig, ApprovalType: &approvalType, + EnableMCP: input.EnableMCP, } if input.State != nil { state := service_dto.ServiceState(*input.State).Int() @@ -520,6 +613,21 @@ func (i *imlServiceModule) Edit(ctx context.Context, id string, input *service_d } } + if input.EnableMCP != nil { + if *input.EnableMCP { + name := info.Name + if input.Name != nil { + name = *input.Name + } + err = i.updateMCPServer(ctx, id, name, "1.0") + if err != nil { + return err + } + } else { + i.deleteMCPServer(ctx, id) + } + } + return nil }) if err != nil { @@ -551,9 +659,15 @@ func (i *imlServiceModule) Delete(ctx context.Context, id string) error { if err != nil { return err } - return client.Hash().Offline(ctx, &gateway.HashRelease{ + + err = client.Hash().Offline(ctx, &gateway.HashRelease{ HashKey: fmt.Sprintf("%s:%s", gateway.KeyServiceMapping, id), }) + if err != nil { + return err + } + i.deleteMCPServer(ctx, id) + return nil }) return err } diff --git a/module/system-apikey/dto/input.go b/module/system-apikey/dto/input.go new file mode 100644 index 00000000..f5c68b87 --- /dev/null +++ b/module/system-apikey/dto/input.go @@ -0,0 +1,14 @@ +package system_apikey_dto + +type Create struct { + Id string `json:"id"` + Name string `json:"name"` + Value string `json:"value"` + Expired int64 `json:"expired"` +} + +type Update struct { + Name *string `json:"name"` + Value *string `json:"value"` + Expired *int64 `json:"expired"` +} diff --git a/module/system-apikey/dto/output.go b/module/system-apikey/dto/output.go new file mode 100644 index 00000000..8ce57917 --- /dev/null +++ b/module/system-apikey/dto/output.go @@ -0,0 +1,50 @@ +package system_apikey_dto + +import ( + system_apikey "github.com/APIParkLab/APIPark/service/system-apikey" + "github.com/eolinker/go-common/auto" +) + +type APIKey struct { + *Item +} + +type Item struct { + *SimpleItem + Creator auto.Label `json:"creator" aolabel:"user"` + Updater auto.Label `json:"updater" aolabel:"user"` + CreateAt auto.TimeLabel `json:"create_time"` + UpdateAt auto.TimeLabel `json:"update_time"` +} + +type SimpleItem struct { + Id string `json:"id"` + Name string `json:"name"` + Value string `json:"value"` + Expired int64 `json:"expired"` +} + +func ToAPIKey(e *system_apikey.APIKey) *APIKey { + return &APIKey{ + Item: ToAPIKeyItem(e), + } +} + +func ToAPIKeySimpleItem(e *system_apikey.APIKey) *SimpleItem { + return &SimpleItem{ + Id: e.Id, + Name: e.Name, + Value: e.Value, + Expired: e.Expired, + } +} + +func ToAPIKeyItem(e *system_apikey.APIKey) *Item { + return &Item{ + SimpleItem: ToAPIKeySimpleItem(e), + Creator: auto.UUID(e.Creator), + Updater: auto.UUID(e.Updater), + CreateAt: auto.TimeLabel(e.CreateAt), + UpdateAt: auto.TimeLabel(e.UpdateAt), + } +} diff --git a/module/system-apikey/iml.go b/module/system-apikey/iml.go new file mode 100644 index 00000000..db5ebdf0 --- /dev/null +++ b/module/system-apikey/iml.go @@ -0,0 +1,69 @@ +package system_apikey + +import ( + "context" + + "github.com/eolinker/go-common/utils" + + "github.com/google/uuid" + + system_apikey "github.com/APIParkLab/APIPark/service/system-apikey" + + system_apikey_dto "github.com/APIParkLab/APIPark/module/system-apikey/dto" +) + +var _ IAPIKeyModule = new(imlAPIKeyModule) + +type imlAPIKeyModule struct { + apikeyService system_apikey.IAPIKeyService `autowired:""` +} + +func (i *imlAPIKeyModule) Create(ctx context.Context, input *system_apikey_dto.Create) error { + if input.Id == "" { + input.Id = uuid.NewString() + } + return i.apikeyService.Create(ctx, &system_apikey.Create{ + Id: input.Id, + Name: input.Name, + Value: input.Value, + Expired: input.Expired, + }) +} + +func (i *imlAPIKeyModule) Update(ctx context.Context, id string, input *system_apikey_dto.Update) error { + return i.apikeyService.Save(ctx, id, &system_apikey.Update{ + Name: input.Name, + Value: input.Value, + Expired: input.Expired, + }) +} + +func (i *imlAPIKeyModule) Delete(ctx context.Context, id string) error { + return i.apikeyService.Delete(ctx, id) +} + +func (i *imlAPIKeyModule) Get(ctx context.Context, id string) (*system_apikey_dto.APIKey, error) { + info, err := i.apikeyService.Get(ctx, id) + if err != nil { + return nil, err + } + return system_apikey_dto.ToAPIKey(info), nil +} + +func (i *imlAPIKeyModule) Search(ctx context.Context, keyword string) ([]*system_apikey_dto.Item, error) { + list, err := i.apikeyService.Search(ctx, keyword, nil, "create_at desc") + if err != nil { + return nil, err + } + + return utils.SliceToSlice(list, system_apikey_dto.ToAPIKeyItem), nil +} + +func (i *imlAPIKeyModule) SimpleList(ctx context.Context) ([]*system_apikey_dto.SimpleItem, error) { + list, err := i.apikeyService.Search(ctx, "", nil, "create_at desc") + if err != nil { + return nil, err + } + + return utils.SliceToSlice(list, system_apikey_dto.ToAPIKeySimpleItem), nil +} diff --git a/module/system-apikey/module.go b/module/system-apikey/module.go new file mode 100644 index 00000000..2e6545e2 --- /dev/null +++ b/module/system-apikey/module.go @@ -0,0 +1,25 @@ +package system_apikey + +import ( + "context" + "reflect" + + "github.com/eolinker/go-common/autowire" + + system_apikey_dto "github.com/APIParkLab/APIPark/module/system-apikey/dto" +) + +type IAPIKeyModule interface { + Create(ctx context.Context, input *system_apikey_dto.Create) error + Update(ctx context.Context, id string, input *system_apikey_dto.Update) error + Delete(ctx context.Context, id string) error + Get(ctx context.Context, id string) (*system_apikey_dto.APIKey, error) + Search(ctx context.Context, keyword string) ([]*system_apikey_dto.Item, error) + SimpleList(ctx context.Context) ([]*system_apikey_dto.SimpleItem, error) +} + +func init() { + autowire.Auto[IAPIKeyModule](func() reflect.Value { + return reflect.ValueOf(new(imlAPIKeyModule)) + }) +} diff --git a/plugins/core/core.go b/plugins/core/core.go index b3afb4b1..761e4913 100644 --- a/plugins/core/core.go +++ b/plugins/core/core.go @@ -1,9 +1,14 @@ package core import ( - ai_model "github.com/APIParkLab/APIPark/controller/ai-model" "net/http" + system_apikey "github.com/APIParkLab/APIPark/controller/system-apikey" + + "github.com/APIParkLab/APIPark/controller/mcp" + + ai_model "github.com/APIParkLab/APIPark/controller/ai-model" + ai_balance "github.com/APIParkLab/APIPark/controller/ai-balance" ai_local "github.com/APIParkLab/APIPark/controller/ai-local" @@ -102,6 +107,8 @@ type plugin struct { settingController system.ISettingController `autowired:""` initController system.IInitController `autowired:""` logController log.ILogController `autowired:""` + mcpController mcp.IMcpController `autowired:""` + systemAPIKeyController system_apikey.IAPIKeyController `autowired:""` apis []pm3.Api } @@ -128,6 +135,8 @@ func (p *plugin) OnComplete() { p.apis = append(p.apis, p.logApis()...) p.apis = append(p.apis, p.aiLocalApis()...) p.apis = append(p.apis, p.aiBalanceAPIs()...) + p.apis = append(p.apis, p.mcpAPIs()...) + p.apis = append(p.apis, p.systemApikeyApis()...) } func (p *plugin) Name() string { diff --git a/plugins/core/mcp.go b/plugins/core/mcp.go new file mode 100644 index 00000000..6da98a9d --- /dev/null +++ b/plugins/core/mcp.go @@ -0,0 +1,22 @@ +package core + +import ( + "fmt" + "net/http" + + "github.com/eolinker/go-common/ignore" + + mcp_server "github.com/APIParkLab/APIPark/mcp-server" + + "github.com/eolinker/go-common/pm3" +) + +func (p *plugin) mcpAPIs() []pm3.Api { + serviceMCPPath := fmt.Sprintf("%s/:serviceId/:event", mcp_server.ServiceBasePath) + ignore.IgnorePath("login", http.MethodGet, serviceMCPPath) + ignore.IgnorePath("login", http.MethodPost, serviceMCPPath) + return []pm3.Api{ + pm3.CreateApiSimple(http.MethodGet, serviceMCPPath, p.mcpController.MCPHandle), + pm3.CreateApiSimple(http.MethodPost, serviceMCPPath, p.mcpController.MCPHandle), + } +} diff --git a/plugins/core/service.go b/plugins/core/service.go index 82ac2107..60a48a69 100644 --- a/plugins/core/service.go +++ b/plugins/core/service.go @@ -11,6 +11,7 @@ import ( func (p *plugin) ServiceApis() []pm3.Api { ignore.IgnorePath("login", http.MethodGet, "/api/v1/service/swagger/:id") + ignore.IgnorePath("login", http.MethodGet, "/api/v1/service/apidoc/:id") return []pm3.Api{ // 项目 pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/service/info", []string{"context", "query:service"}, []string{"service"}, p.serviceController.Get, access.SystemWorkspaceServiceViewAll, access.TeamTeamServiceView), @@ -36,6 +37,7 @@ func (p *plugin) ServiceApis() []pm3.Api { pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/service/doc", []string{"context", "query:service"}, []string{"doc"}, p.serviceController.ServiceDoc, access.SystemWorkspaceServiceViewAll, access.TeamServiceServiceIntroView), pm3.CreateApiWidthDoc(http.MethodPut, "/api/v1/service/doc", []string{"context", "query:service", "body"}, nil, p.serviceController.SaveServiceDoc, access.SystemWorkspaceServiceManagerAll, access.TeamServiceServiceIntroManager), pm3.CreateApiSimple(http.MethodGet, "/api/v1/service/swagger/:id", p.serviceController.Swagger), + pm3.CreateApiSimple(http.MethodGet, "/api/v1/service/apidoc/:id", p.serviceController.Swagger), pm3.CreateApiSimple(http.MethodGet, "/api/v1/export/openapi/:id", p.serviceController.ExportSwagger), } } diff --git a/plugins/core/system.go b/plugins/core/system.go index a44a921c..05560709 100644 --- a/plugins/core/system.go +++ b/plugins/core/system.go @@ -15,3 +15,14 @@ func (p *plugin) systemApis() []pm3.Api { pm3.CreateApiWidthDoc(http.MethodPost, "/api/v1/system/general", []string{"context", "body"}, nil, p.settingController.Set), } } + +func (p *plugin) systemApikeyApis() []pm3.Api { + return []pm3.Api{ + pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/system/apikey", []string{"context", "query:apikey"}, []string{"apikey"}, p.systemAPIKeyController.Get), + pm3.CreateApiWidthDoc(http.MethodPost, "/api/v1/system/apikey", []string{"context", "body"}, nil, p.systemAPIKeyController.Create), + pm3.CreateApiWidthDoc(http.MethodPut, "/api/v1/system/apikey", []string{"context", "query:apikey", "body"}, nil, p.systemAPIKeyController.Update), + pm3.CreateApiWidthDoc(http.MethodDelete, "/api/v1/system/apikey", []string{"context", "query:apikey"}, nil, p.systemAPIKeyController.Delete), + pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/system/apikeys", []string{"context", "query:keyword"}, []string{"apikeys"}, p.systemAPIKeyController.Search), + pm3.CreateApiWidthDoc(http.MethodGet, "/api/v1/simple/system/apikeys", []string{"context"}, []string{"apikeys"}, p.systemAPIKeyController.SimpleList), + } +} diff --git a/plugins/openapi/check.go b/plugins/openapi/check.go index 4f5f50f7..b05031e5 100644 --- a/plugins/openapi/check.go +++ b/plugins/openapi/check.go @@ -2,6 +2,13 @@ package openapi import ( "strings" + "time" + + "github.com/eolinker/go-common/ignore" + + "github.com/eolinker/go-common/autowire" + + system_apikey "github.com/APIParkLab/APIPark/module/system-apikey" "github.com/eolinker/eosc/env" @@ -14,7 +21,8 @@ var ( ) type openapiCheck struct { - apikey string + apikey string + apikeyModule system_apikey.IAPIKeyModule `autowired:""` } func newOpenapiCheck() *openapiCheck { @@ -22,7 +30,9 @@ func newOpenapiCheck() *openapiCheck { if !has { apikey = defaultAPIKey } - return &openapiCheck{apikey: apikey} + p := &openapiCheck{apikey: apikey} + autowire.Autowired(p) + return p } func (o *openapiCheck) Check(method string, path string) (bool, []gin.HandlerFunc) { @@ -37,9 +47,38 @@ func (o *openapiCheck) Sort() int { } func (o *openapiCheck) Handler(ginCtx *gin.Context) { + notIgnore := !ignore.IsIgnorePath("openapi", ginCtx.Request.Method, ginCtx.FullPath()) + if !notIgnore { + return + } authorization := ginCtx.GetHeader("Authorization") if authorization == "" { + apikey, has := ginCtx.GetQuery("apikey") + if !has { + ginCtx.AbortWithStatusJSON(403, gin.H{"code": -8, "msg": "invalid token", "success": "fail"}) + return + } + authorization = apikey + } + if authorization == o.apikey { + return + } + list, err := o.apikeyModule.SimpleList(ginCtx) + if err != nil { ginCtx.AbortWithStatusJSON(403, gin.H{"code": -8, "msg": "invalid token", "success": "fail"}) return } + if len(list) == 0 { + ginCtx.AbortWithStatusJSON(403, gin.H{"code": -8, "msg": "invalid token", "success": "fail"}) + return + } + for _, item := range list { + if item.Value == authorization { + if item.Expired != 0 && item.Expired < time.Now().Unix() { + continue + } + return + } + } + ginCtx.AbortWithStatusJSON(403, gin.H{"code": -8, "msg": "invalid token", "success": "fail"}) } diff --git a/plugins/openapi/mcp.go b/plugins/openapi/mcp.go new file mode 100644 index 00000000..3a230c68 --- /dev/null +++ b/plugins/openapi/mcp.go @@ -0,0 +1,20 @@ +package openapi + +import ( + "fmt" + "net/http" + + "github.com/eolinker/go-common/ignore" + + mcp_server "github.com/APIParkLab/APIPark/mcp-server" + "github.com/eolinker/go-common/pm3" +) + +func (p *plugin) mcpAPIs() []pm3.Api { + messagePath := fmt.Sprintf("%s/message", mcp_server.GlobalBasePath) + ignore.IgnorePath("openapi", http.MethodPost, messagePath) + return []pm3.Api{ + pm3.CreateApiSimple(http.MethodGet, fmt.Sprintf("%s/sse", mcp_server.GlobalBasePath), p.mcpController.GlobalMCPHandle), + pm3.CreateApiSimple(http.MethodPost, messagePath, p.mcpController.GlobalMCPHandle), + } +} diff --git a/plugins/openapi/plugin.go b/plugins/openapi/plugin.go index 3082fb84..45303d52 100644 --- a/plugins/openapi/plugin.go +++ b/plugins/openapi/plugin.go @@ -2,6 +2,7 @@ package openapi import ( application_authorization "github.com/APIParkLab/APIPark/controller/application-authorization" + "github.com/APIParkLab/APIPark/controller/mcp" "github.com/eolinker/go-common/pm3" ) @@ -13,6 +14,7 @@ var ( type plugin struct { apis []pm3.Api authorizationController application_authorization.IAuthorizationController `autowired:""` + mcpController mcp.IMcpController `autowired:""` } func (p *plugin) Middlewares() []pm3.IMiddleware { @@ -30,4 +32,5 @@ func (p *plugin) Name() string { } func (p *plugin) OnComplete() { p.apis = p.appAuthorizationApis() + p.apis = append(p.apis, p.mcpAPIs()...) } diff --git a/service/service/iml.go b/service/service/iml.go index 8e42fea6..0e4eb76a 100644 --- a/service/service/iml.go +++ b/service/service/iml.go @@ -178,6 +178,7 @@ func createEntityHandler(i *Create) *service.Service { Catalogue: i.Catalogue, AsServer: i.AsServer, AsApp: i.AsApp, + EnableMCP: i.EnableMCP, } } func updateHandler(e *service.Service, i *Edit) { @@ -209,5 +210,8 @@ func updateHandler(e *service.Service, i *Edit) { if i.State != nil { e.State = *i.State } + if i.EnableMCP != nil { + e.EnableMCP = *i.EnableMCP + } e.UpdateAt = time.Now() } diff --git a/service/service/model.go b/service/service/model.go index bd8a1b3b..973c3b4e 100644 --- a/service/service/model.go +++ b/service/service/model.go @@ -122,6 +122,7 @@ type Service struct { State int AsServer bool AsApp bool + EnableMCP bool ApprovalType ApprovalType CreateTime time.Time UpdateTime time.Time @@ -145,6 +146,7 @@ func FromEntity(e *service.Service) *Service { ApprovalType: ToApprovalType(e.ApprovalType), AsServer: e.AsServer, AsApp: e.AsApp, + EnableMCP: e.EnableMCP, State: e.State, CreateTime: e.CreateAt, UpdateTime: e.UpdateAt, @@ -167,6 +169,7 @@ type Create struct { State int AsServer bool AsApp bool + EnableMCP bool } type Edit struct { @@ -179,6 +182,7 @@ type Edit struct { AdditionalConfig *map[string]string State *int ApprovalType *ApprovalType + EnableMCP *bool } type CreateTag struct { diff --git a/service/system-apikey/iml.go b/service/system-apikey/iml.go new file mode 100644 index 00000000..5fdbe57a --- /dev/null +++ b/service/system-apikey/iml.go @@ -0,0 +1,64 @@ +package system_apikey + +import ( + "time" + + "github.com/APIParkLab/APIPark/service/universally" + "github.com/APIParkLab/APIPark/stores/system" +) + +type imlAPIKeyService struct { + store system.IAPIKeyStore `autowired:""` + universally.IServiceGet[APIKey] + universally.IServiceDelete + universally.IServiceCreate[Create] + universally.IServiceEdit[Update] +} + +func (i *imlAPIKeyService) OnComplete() { + i.IServiceGet = universally.NewGet[APIKey, system.APIKey](i.store, FromEntity) + i.IServiceCreate = universally.NewCreator[Create, system.APIKey](i.store, "system_apikey", i.createEntityHandler, i.uniquestHandler, i.labelHandler) + i.IServiceDelete = universally.NewDelete[system.APIKey](i.store) + i.IServiceEdit = universally.NewEdit[Update, system.APIKey](i.store, i.updateHandler, i.labelHandler) +} + +func (i *imlAPIKeyService) idHandler(e *system.APIKey) int64 { + return e.Id +} +func (i *imlAPIKeyService) labelHandler(e *system.APIKey) []string { + return []string{e.Name} +} +func (i *imlAPIKeyService) uniquestHandler(t *Create) []map[string]interface{} { + return []map[string]interface{}{{"uuid": t.Id}} +} +func (i *imlAPIKeyService) createEntityHandler(t *Create) *system.APIKey { + now := time.Now() + return &system.APIKey{ + UUID: t.Id, + Name: t.Name, + Value: t.Value, + Expired: t.Expired, + CreateAt: now, + UpdateAt: now, + } +} + +func (i *imlAPIKeyService) updateHandler(e *system.APIKey, t *Update) { + isUpdate := false + if t.Name != nil { + e.Name = *t.Name + isUpdate = true + } + if t.Value != nil { + e.Value = *t.Value + isUpdate = true + } + if t.Expired != nil { + e.Expired = *t.Expired + isUpdate = true + } + if isUpdate { + e.UpdateAt = time.Now() + } + +} diff --git a/service/system-apikey/model.go b/service/system-apikey/model.go new file mode 100644 index 00000000..15e67e51 --- /dev/null +++ b/service/system-apikey/model.go @@ -0,0 +1,44 @@ +package system_apikey + +import ( + "time" + + "github.com/APIParkLab/APIPark/stores/system" +) + +type APIKey struct { + Id string + Name string + Value string + Creator string + Updater string + CreateAt time.Time + UpdateAt time.Time + Expired int64 +} + +type Create struct { + Id string + Name string + Value string + Expired int64 +} + +type Update struct { + Name *string + Value *string + Expired *int64 +} + +func FromEntity(s *system.APIKey) *APIKey { + return &APIKey{ + Id: s.UUID, + Name: s.Name, + Value: s.Value, + Creator: s.Creator, + Updater: s.Updater, + CreateAt: s.CreateAt, + UpdateAt: s.UpdateAt, + Expired: s.Expired, + } +} diff --git a/service/system-apikey/service.go b/service/system-apikey/service.go new file mode 100644 index 00000000..b195ec34 --- /dev/null +++ b/service/system-apikey/service.go @@ -0,0 +1,21 @@ +package system_apikey + +import ( + "reflect" + + "github.com/APIParkLab/APIPark/service/universally" + "github.com/eolinker/go-common/autowire" +) + +type IAPIKeyService interface { + universally.IServiceGet[APIKey] + universally.IServiceDelete + universally.IServiceCreate[Create] + universally.IServiceEdit[Update] +} + +func init() { + autowire.Auto[IAPIKeyService](func() reflect.Value { + return reflect.ValueOf(new(imlAPIKeyService)) + }) +} diff --git a/stores/service/model.go b/stores/service/model.go index 3e205ace..8ed2f0fd 100644 --- a/stores/service/model.go +++ b/stores/service/model.go @@ -24,6 +24,7 @@ type Service struct { ApprovalType int `gorm:"type:tinyint(4);not null;column:approval_type;comment:审核类型"` AsServer bool `gorm:"type:tinyint(1);not null;column:as_server;comment:是否为服务端项目"` AsApp bool `gorm:"type:tinyint(1);not null;column:as_app;comment:是否为应用项目"` + EnableMCP bool `gorm:"type:tinyint(1);not null;column:enable_mcp;comment:是否启用MCP"` } func (p *Service) IdValue() int64 { diff --git a/stores/system/model.go b/stores/system/model.go new file mode 100644 index 00000000..3929202f --- /dev/null +++ b/stores/system/model.go @@ -0,0 +1,22 @@ +package system + +import "time" + +type APIKey struct { + Id int64 `gorm:"column:id;type:BIGINT(20);AUTO_INCREMENT;NOT NULL;comment:id;primary_key;comment:主键ID;"` + UUID string `gorm:"type:varchar(36);not null;column:uuid;uniqueIndex:uuid;comment:UUID;"` + Name string `gorm:"type:varchar(100);not null;column:name;comment:name"` + Value string `gorm:"type:text;not null;column:value;comment:value;"` + Expired int64 `gorm:"type:int(11);not null;column:expired;comment:过期时间"` // 过期时间 + Creator string `gorm:"size:36;not null;column:creator;comment:创建人id" aovalue:"creator"` // 创建人id + Updater string `gorm:"size:36;not null;column:updater;comment:修改人id" aovalue:"updater"` // 修改人id + CreateAt time.Time `gorm:"type:timestamp;NOT NULL;DEFAULT:CURRENT_TIMESTAMP;column:create_at;comment:创建时间"` + UpdateAt time.Time `gorm:"type:timestamp;NOT NULL;DEFAULT:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;column:update_at;comment:修改时间"` +} + +func (a *APIKey) IdValue() int64 { + return a.Id +} +func (a *APIKey) TableName() string { + return "system_apikey" +} diff --git a/stores/system/store.go b/stores/system/store.go new file mode 100644 index 00000000..1526a93d --- /dev/null +++ b/stores/system/store.go @@ -0,0 +1,22 @@ +package system + +import ( + "reflect" + + "github.com/eolinker/go-common/autowire" + "github.com/eolinker/go-common/store" +) + +type IAPIKeyStore interface { + store.ISearchStore[APIKey] +} + +type imlAPIKeyStore struct { + store.SearchStore[APIKey] +} + +func init() { + autowire.Auto[IAPIKeyStore](func() reflect.Value { + return reflect.ValueOf(new(imlAPIKeyStore)) + }) +} diff --git a/unique_parameters.json b/unique_parameters.json new file mode 100644 index 00000000..ce23b7c7 --- /dev/null +++ b/unique_parameters.json @@ -0,0 +1,44 @@ +{ + "context_length_exceeded_behavior": "", + "count_penalty": "", + "disable_search": "", + "do_sample": "", + "enable_enhance": "", + "enable_search": "", + "frequency_penalt": "frequency_penalty", + "frequency_penalty": "frequency_penalty", + "json_schema": "json_schema", + "k": "", + "logprobs": "", + "mask_sensitive_info": "", + "maxTokenCount": "max_tokens", + "maxTokens": "max_tokens", + "max_gen_len": "max_tokens", + "max_new_tokens": "max_tokens", + "max_output_tokens": "max_tokens", + "max_tokens": "max_tokens", + "max_tokens_to_sample": "max_tokens", + "min_output_tokens": "max_tokens", + "p": "top_p", + "plugin_web_search": "", + "preamble_override": "", + "presence_penalty": "presence_penalty", + "prompt_truncation": "", + "random_seed": "", + "reasoning_effort": "", + "repetition_penalty": "", + "res_format": "", + "response_format": "", + "return_type": "", + "safe_prompt": "", + "seed": "", + "show_ref_label": "", + "stream": "", + "temperature": "temperature", + "topP": "top_p", + "top_k": "", + "top_logprobs": "", + "top_p": "top_p", + "web_search": "", + "with_search_enhance": "" +} \ No newline at end of file diff --git a/update_parameter_mappings.py b/update_parameter_mappings.py new file mode 100644 index 00000000..bde477e5 --- /dev/null +++ b/update_parameter_mappings.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +import os +import json +from pathlib import Path + +# 基础路径 +base_path = "/Users/liujian/work/golang/src/github.com/APIParkLab/APIPark/ai-provider/model-runtime/model-providers" + +# OpenAI 参数列表 +openai_params = [ + "max_tokens", + "max_completion_tokens", + "temperature", + "top_p", + "n", + "stream", + "stop", + "presence_penalty", + "response_format", + "seed", + "frequency_penalty" +] + +# 参数映射关系 +param_mapping = { + "temperature": "temperature", + "top_p": "top_p", + "top_k": "", + "max_tokens": "max_tokens", + "presence_penalty": "presence_penalty", + "frequency_penalty": "frequency_penalty", + "response_format": "response_format", + "res_format": "response_format", + "p": "top_p", + "k": "", + "repetition_penalty": "frequency_penalty", + "reasoning_effort": "", + "enable_enhance": "", + "with_search_enhance": "", + "vision_support": "", + "function_call_support": "", + "context_size": "", + "mode": "", + "max_completion_tokens": "max_completion_tokens", + "seed": "seed", + "n": "n", + "stream": "stream", + "stop": "stop" +} + +# 遍历所有供应商目录 +for provider_dir in os.listdir(base_path): + provider_path = os.path.join(base_path, provider_dir) + + # 检查是否是目录 + if not os.path.isdir(provider_path): + continue + + # 检查parameter_names.json文件是否存在 + param_file = os.path.join(provider_path, "parameter_names.json") + if not os.path.isfile(param_file): + continue + + try: + # 读取parameter_names.json文件 + with open(param_file, 'r', encoding='utf-8') as f: + params = json.load(f) + + # 更新映射关系 + for param in params: + if param in param_mapping: + params[param] = param_mapping[param] + + # 保存更新后的文件 + with open(param_file, 'w', encoding='utf-8') as f: + json.dump(params, f, indent=2, ensure_ascii=False) + + print(f"Updated parameter mappings for {provider_dir}") + except Exception as e: + print(f"Error processing {param_file}: {e}") \ No newline at end of file