Add ovms support

This commit is contained in:
John Feng 2026-04-10 23:37:51 +08:00
parent 5e44a99410
commit 793357c61e
10 changed files with 37 additions and 10 deletions

View file

@ -25,10 +25,11 @@ Examples:
picoclaw model # Show current default model picoclaw model # Show current default model
picoclaw model gpt-5.2 # Set gpt-5.2 as default picoclaw model gpt-5.2 # Set gpt-5.2 as default
picoclaw model claude-sonnet-4.6 # Set claude-sonnet-4.6 as default picoclaw model claude-sonnet-4.6 # Set claude-sonnet-4.6 as default
picoclaw model local-model # Set local VLLM server as default picoclaw model local-model # Set local VLLM/OVMS server as default
Note: 'local-model' is a special value for using a local VLLM server Note: 'local-model' is a special value for using a local VLLM server
(running at localhost:8000 by default) which does not require an API key.`, (running at localhost:8000 by default) which does not require an API key.
You can also use OVMS (OpenVINO Model Server) or other OpenAI-compatible servers.`,
Args: cobra.MaximumNArgs(1), Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
configPath := internal.GetConfigPath() configPath := internal.GetConfigPath()

View file

@ -26,7 +26,7 @@ func supportsAudioTranscription(model string) bool {
case "openai", "azure", "azure-openai", case "openai", "azure", "azure-openai",
"litellm", "openrouter", "groq", "zhipu", "gemini", "nvidia", "litellm", "openrouter", "groq", "zhipu", "gemini", "nvidia",
"ollama", "moonshot", "shengsuanyun", "deepseek", "cerebras", "ollama", "moonshot", "shengsuanyun", "deepseek", "cerebras",
"vivgrid", "volcengine", "vllm", "qwen", "qwen-intl", "qwen-international", "dashscope-intl", "vivgrid", "volcengine", "vllm", "ovms", "qwen", "qwen-intl", "qwen-international", "dashscope-intl",
"qwen-us", "dashscope-us", "mistral", "avian", "minimax", "longcat", "modelscope", "novita", "qwen-us", "dashscope-us", "mistral", "avian", "minimax", "longcat", "modelscope", "novita",
"coding-plan", "alibaba-coding", "qwen-coding": "coding-plan", "alibaba-coding", "qwen-coding":
// These protocols all go through the OpenAI-compatible or Azure provider path in // These protocols all go through the OpenAI-compatible or Azure provider path in
@ -47,7 +47,7 @@ func supportsWhisperTranscription(model string) bool {
switch protocol { switch protocol {
case "openai", "litellm", "openrouter", "groq", "zhipu", "gemini", "nvidia", case "openai", "litellm", "openrouter", "groq", "zhipu", "gemini", "nvidia",
"ollama", "moonshot", "shengsuanyun", "deepseek", "cerebras", "ollama", "moonshot", "shengsuanyun", "deepseek", "cerebras",
"vivgrid", "volcengine", "vllm", "qwen", "qwen-intl", "qwen-international", "dashscope-intl", "vivgrid", "volcengine", "vllm", "ovms", "qwen", "qwen-intl", "qwen-international", "dashscope-intl",
"qwen-us", "dashscope-us", "mistral", "avian", "minimax", "longcat", "modelscope", "novita", "qwen-us", "dashscope-us", "mistral", "avian", "minimax", "longcat", "modelscope", "novita",
"coding-plan", "alibaba-coding", "qwen-coding", "mimo": "coding-plan", "alibaba-coding", "qwen-coding", "mimo":
return true return true

View file

@ -347,6 +347,13 @@ func DefaultConfig() *Config {
APIBase: "http://localhost:8000/v1", APIBase: "http://localhost:8000/v1",
}, },
// OVMS (local) - http://localhost:8000
{
ModelName: "local-ovms",
Model: "ovms/custom-model",
APIBase: "http://localhost:8000/v3",
},
// LM Studio (local) - http://localhost:1234 // LM Studio (local) - http://localhost:1234
{ {
ModelName: "lmstudio-local", ModelName: "lmstudio-local",

View file

@ -52,6 +52,7 @@ var protocolMetaByName = map[string]protocolMeta{
"coding-plan-anthropic": {defaultAPIBase: "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic"}, "coding-plan-anthropic": {defaultAPIBase: "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic"},
"alibaba-coding-anthropic": {defaultAPIBase: "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic"}, "alibaba-coding-anthropic": {defaultAPIBase: "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic"},
"vllm": {defaultAPIBase: "http://localhost:8000/v1", emptyAPIKeyAllowed: true}, "vllm": {defaultAPIBase: "http://localhost:8000/v1", emptyAPIKeyAllowed: true},
"ovms": {defaultAPIBase: "http://localhost:8000/v3", emptyAPIKeyAllowed: true},
"mistral": {defaultAPIBase: "https://api.mistral.ai/v1"}, "mistral": {defaultAPIBase: "https://api.mistral.ai/v1"},
"avian": {defaultAPIBase: "https://api.avian.io/v1"}, "avian": {defaultAPIBase: "https://api.avian.io/v1"},
"minimax": {defaultAPIBase: "https://api.minimaxi.com/v1"}, "minimax": {defaultAPIBase: "https://api.minimaxi.com/v1"},
@ -220,7 +221,7 @@ func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, err
case "litellm", "lmstudio", "openrouter", "groq", "zhipu", "gemini", "nvidia", "venice", case "litellm", "lmstudio", "openrouter", "groq", "zhipu", "gemini", "nvidia", "venice",
"ollama", "moonshot", "shengsuanyun", "deepseek", "cerebras", "ollama", "moonshot", "shengsuanyun", "deepseek", "cerebras",
"vivgrid", "volcengine", "vllm", "qwen", "qwen-intl", "qwen-international", "dashscope-intl", "vivgrid", "volcengine", "vllm", "ovms", "qwen", "qwen-intl", "qwen-international", "dashscope-intl",
"qwen-us", "dashscope-us", "mistral", "avian", "longcat", "modelscope", "novita", "qwen-us", "dashscope-us", "mistral", "avian", "longcat", "modelscope", "novita",
"coding-plan", "alibaba-coding", "qwen-coding", "mimo": "coding-plan", "alibaba-coding", "qwen-coding", "mimo":
// All other OpenAI-compatible HTTP providers // All other OpenAI-compatible HTTP providers

View file

@ -120,6 +120,7 @@ func TestCreateProviderFromConfig_DefaultAPIBase(t *testing.T) {
{"vivgrid", "vivgrid"}, {"vivgrid", "vivgrid"},
{"qwen", "qwen"}, {"qwen", "qwen"},
{"vllm", "vllm"}, {"vllm", "vllm"},
{"ovms", "ovms"},
{"deepseek", "deepseek"}, {"deepseek", "deepseek"},
{"ollama", "ollama"}, {"ollama", "ollama"},
{"lmstudio", "lmstudio"}, {"lmstudio", "lmstudio"},
@ -237,6 +238,20 @@ func TestCreateProviderFromConfig_LocalProviders(t *testing.T) {
apiKey: "", apiKey: "",
wantModelID: "Qwen/Qwen3-8B", wantModelID: "Qwen/Qwen3-8B",
}, },
{
name: "OVMS with API key",
modelName: "test-ovms",
model: "ovms/llama3",
apiKey: "test-key",
wantModelID: "llama3",
},
{
name: "OVMS without API key",
modelName: "test-ovms",
model: "ovms/llama3",
apiKey: "",
wantModelID: "llama3",
},
} }
for _, tt := range tests { for _, tt := range tests {

View file

@ -357,7 +357,7 @@ Check these in the dashboard:
- a default model is selected - a default model is selected
- the model has credentials or OAuth state - the model has credentials or OAuth state
- local models such as Ollama or vLLM are reachable - local models such as Ollama, vLLM, or OVMS are reachable
### The launcher cannot find `picoclaw` ### The launcher cannot find `picoclaw`

View file

@ -176,7 +176,7 @@ func runLocalModelProbe(m *config.ModelConfig) bool {
switch protocol { switch protocol {
case "ollama": case "ollama":
return probeOllamaModelFunc(apiBase, modelID) return probeOllamaModelFunc(apiBase, modelID)
case "vllm", "lmstudio": case "vllm", "ovms", "lmstudio":
return probeOpenAICompatibleModelFunc(apiBase, modelID, m.APIKey()) return probeOpenAICompatibleModelFunc(apiBase, modelID, m.APIKey())
case "github-copilot", "copilot": case "github-copilot", "copilot":
return probeTCPServiceFunc(apiBase) return probeTCPServiceFunc(apiBase)

View file

@ -30,9 +30,10 @@ const PROVIDER_PRIORITY: Record<string, number> = {
shengsuanyun: 14, shengsuanyun: 14,
ollama: 15, ollama: 15,
vllm: 16, vllm: 16,
mistral: 17, ovms: 17,
avian: 18, mistral: 18,
mimo: 19, avian: 19,
mimo: 20,
} }
interface ProviderGroup { interface ProviderGroup {

View file

@ -36,6 +36,7 @@ const PROVIDER_DOMAINS: Record<string, string> = {
mistral: "mistral.ai", mistral: "mistral.ai",
avian: "avian.io", avian: "avian.io",
vllm: "vllm.ai", vllm: "vllm.ai",
ovms: "docs.openvino.ai",
zhipu: "zhipuai.cn", zhipu: "zhipuai.cn",
mimo: "xiaomi.com", mimo: "xiaomi.com",
} }

View file

@ -17,6 +17,7 @@ const PROVIDER_LABELS: Record<string, string> = {
mistral: "Mistral AI", mistral: "Mistral AI",
avian: "Avian", avian: "Avian",
vllm: "VLLM (local)", vllm: "VLLM (local)",
ovms: "OVMS (local)",
zhipu: "Zhipu AI (智谱)", zhipu: "Zhipu AI (智谱)",
mimo: "Xiaomi MiMo", mimo: "Xiaomi MiMo",
} }