diff --git a/cmd/picoclaw/internal/model/command.go b/cmd/picoclaw/internal/model/command.go index 330734b82..72200bbe5 100644 --- a/cmd/picoclaw/internal/model/command.go +++ b/cmd/picoclaw/internal/model/command.go @@ -25,10 +25,11 @@ Examples: picoclaw model # Show current default model 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 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 -(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), RunE: func(cmd *cobra.Command, args []string) error { configPath := internal.GetConfigPath() diff --git a/pkg/audio/asr/asr.go b/pkg/audio/asr/asr.go index 1482f40bb..0837d4b55 100644 --- a/pkg/audio/asr/asr.go +++ b/pkg/audio/asr/asr.go @@ -26,7 +26,7 @@ func supportsAudioTranscription(modelCfg *config.ModelConfig) bool { case "openai", "azure", "azure-openai", "litellm", "openrouter", "groq", "zhipu", "gemini", "nvidia", "ollama", "moonshot", "shengsuanyun", "deepseek", "cerebras", - "vivgrid", "volcengine", "vllm", "qwen", "qwen-portal", "qwen-intl", "qwen-international", "dashscope-intl", + "vivgrid", "volcengine", "vllm", "ovms", "qwen", "qwen-portal", "qwen-intl", "qwen-international", "dashscope-intl", "qwen-us", "dashscope-us", "mistral", "avian", "minimax", "longcat", "modelscope", "novita", "coding-plan", "alibaba-coding", "qwen-coding", "zai": // These protocols all go through the OpenAI-compatible or Azure provider path in @@ -47,7 +47,11 @@ func supportsWhisperTranscription(modelCfg *config.ModelConfig) bool { switch protocol { case "openai", "litellm", "openrouter", "groq", "zhipu", "gemini", "nvidia", "ollama", "moonshot", "shengsuanyun", "deepseek", "cerebras", +<<<<<<< HEAD "vivgrid", "volcengine", "vllm", "qwen", "qwen-portal", "qwen-intl", "qwen-international", "dashscope-intl", +======= + "vivgrid", "volcengine", "vllm", "ovms", "qwen", "qwen-intl", "qwen-international", "dashscope-intl", +>>>>>>> 793357c6 (Add ovms support) "qwen-us", "dashscope-us", "mistral", "avian", "minimax", "longcat", "modelscope", "novita", "coding-plan", "alibaba-coding", "qwen-coding", "zai", "mimo": return true diff --git a/pkg/config/defaults.go b/pkg/config/defaults.go index be8c32495..d461ac447 100644 --- a/pkg/config/defaults.go +++ b/pkg/config/defaults.go @@ -271,6 +271,13 @@ func DefaultConfig() *Config { 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 { ModelName: "lmstudio-local", diff --git a/pkg/providers/factory_provider.go b/pkg/providers/factory_provider.go index ce83c6c54..0b398d797 100644 --- a/pkg/providers/factory_provider.go +++ b/pkg/providers/factory_provider.go @@ -54,6 +54,7 @@ var protocolMetaByName = map[string]protocolMeta{ "alibaba-coding-anthropic": {defaultAPIBase: "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic"}, "zai": {defaultAPIBase: "https://api.z.ai/api/coding/paas/v4"}, "vllm": {defaultAPIBase: "http://localhost:8000/v1", emptyAPIKeyAllowed: true}, + "ovms": {defaultAPIBase: "http://localhost:8000/v3", emptyAPIKeyAllowed: true}, "mistral": {defaultAPIBase: "https://api.mistral.ai/v1"}, "avian": {defaultAPIBase: "https://api.avian.io/v1"}, "minimax": {defaultAPIBase: "https://api.minimaxi.com/v1"}, @@ -248,7 +249,7 @@ func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, err case "litellm", "lmstudio", "openrouter", "groq", "zhipu", "nvidia", "venice", "ollama", "moonshot", "shengsuanyun", "deepseek", "cerebras", - "vivgrid", "volcengine", "vllm", "qwen", "qwen-portal", "qwen-intl", "qwen-international", "dashscope-intl", + "vivgrid", "volcengine", "vllm", "ovms", "qwen", "qwen-portal", "qwen-intl", "qwen-international", "dashscope-intl", "qwen-us", "dashscope-us", "mistral", "avian", "longcat", "modelscope", "novita", "coding-plan", "alibaba-coding", "qwen-coding", "zai", "mimo": // All other OpenAI-compatible HTTP providers diff --git a/pkg/providers/factory_provider_test.go b/pkg/providers/factory_provider_test.go index 3dd1eefb3..4b014ab36 100644 --- a/pkg/providers/factory_provider_test.go +++ b/pkg/providers/factory_provider_test.go @@ -199,6 +199,7 @@ func TestCreateProviderFromConfig_DefaultAPIBase(t *testing.T) { {"vivgrid", "vivgrid"}, {"qwen", "qwen"}, {"vllm", "vllm"}, + {"ovms", "ovms"}, {"deepseek", "deepseek"}, {"ollama", "ollama"}, {"lmstudio", "lmstudio"}, @@ -316,6 +317,20 @@ func TestCreateProviderFromConfig_LocalProviders(t *testing.T) { apiKey: "", 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 { diff --git a/web/README.md b/web/README.md index 2a57524e0..b584794ca 100644 --- a/web/README.md +++ b/web/README.md @@ -341,7 +341,7 @@ Check these in the dashboard: - a default model is selected - 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` diff --git a/web/backend/api/model_status.go b/web/backend/api/model_status.go index d262cf124..da8110466 100644 --- a/web/backend/api/model_status.go +++ b/web/backend/api/model_status.go @@ -176,7 +176,7 @@ func runLocalModelProbe(m *config.ModelConfig) bool { switch protocol { case "ollama": return probeOllamaModelFunc(apiBase, modelID) - case "vllm", "lmstudio": + case "vllm", "ovms", "lmstudio": return probeOpenAICompatibleModelFunc(apiBase, modelID, m.APIKey()) case "github-copilot", "copilot": return probeTCPServiceFunc(apiBase) diff --git a/web/frontend/src/components/models/models-page.tsx b/web/frontend/src/components/models/models-page.tsx index 152c47585..df1655320 100644 --- a/web/frontend/src/components/models/models-page.tsx +++ b/web/frontend/src/components/models/models-page.tsx @@ -42,9 +42,10 @@ const PROVIDER_PRIORITY: Record = { azure: 23, ollama: 24, vllm: 25, - lmstudio: 26, - zai: 27, - mimo: 28, + ovms: 26, + lmstudio: 27, + zai: 28, + mimo: 29, } interface ProviderGroup { diff --git a/web/frontend/src/components/models/provider-icon.tsx b/web/frontend/src/components/models/provider-icon.tsx index 8d1cfe2c9..1e5250855 100644 --- a/web/frontend/src/components/models/provider-icon.tsx +++ b/web/frontend/src/components/models/provider-icon.tsx @@ -41,6 +41,7 @@ const PROVIDER_DOMAINS: Record = { mistral: "mistral.ai", avian: "avian.io", vllm: "vllm.ai", + ovms: "docs.openvino.ai", zhipu: "zhipuai.cn", zai: "z.ai", mimo: "xiaomi.com", diff --git a/web/frontend/src/components/models/provider-label.ts b/web/frontend/src/components/models/provider-label.ts index 123640fe5..d72eebfc0 100644 --- a/web/frontend/src/components/models/provider-label.ts +++ b/web/frontend/src/components/models/provider-label.ts @@ -20,6 +20,7 @@ const PROVIDER_LABELS: Record = { mistral: "Mistral AI", avian: "Avian", vllm: "VLLM (local)", + ovms: "OVMS (local)", zhipu: "Zhipu AI (智谱)", zai: "Z.ai", mimo: "Xiaomi MiMo",