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 d15dc3f09..9ec3373be 100644 --- a/pkg/audio/asr/asr.go +++ b/pkg/audio/asr/asr.go @@ -26,7 +26,7 @@ func supportsAudioTranscription(model string) bool { case "openai", "azure", "azure-openai", "litellm", "openrouter", "groq", "zhipu", "gemini", "nvidia", "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", "coding-plan", "alibaba-coding", "qwen-coding": // These protocols all go through the OpenAI-compatible or Azure provider path in @@ -47,7 +47,7 @@ func supportsWhisperTranscription(model string) bool { switch protocol { case "openai", "litellm", "openrouter", "groq", "zhipu", "gemini", "nvidia", "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", "coding-plan", "alibaba-coding", "qwen-coding", "mimo": return true diff --git a/pkg/config/defaults.go b/pkg/config/defaults.go index bb073d436..f63c9efd2 100644 --- a/pkg/config/defaults.go +++ b/pkg/config/defaults.go @@ -347,6 +347,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 f13dc646c..862e8f5db 100644 --- a/pkg/providers/factory_provider.go +++ b/pkg/providers/factory_provider.go @@ -52,6 +52,7 @@ var protocolMetaByName = map[string]protocolMeta{ "coding-plan-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}, + "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"}, @@ -220,7 +221,7 @@ func CreateProviderFromConfig(cfg *config.ModelConfig) (LLMProvider, string, err case "litellm", "lmstudio", "openrouter", "groq", "zhipu", "gemini", "nvidia", "venice", "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", "coding-plan", "alibaba-coding", "qwen-coding", "mimo": // All other OpenAI-compatible HTTP providers diff --git a/pkg/providers/factory_provider_test.go b/pkg/providers/factory_provider_test.go index c362463ae..e22399a07 100644 --- a/pkg/providers/factory_provider_test.go +++ b/pkg/providers/factory_provider_test.go @@ -120,6 +120,7 @@ func TestCreateProviderFromConfig_DefaultAPIBase(t *testing.T) { {"vivgrid", "vivgrid"}, {"qwen", "qwen"}, {"vllm", "vllm"}, + {"ovms", "ovms"}, {"deepseek", "deepseek"}, {"ollama", "ollama"}, {"lmstudio", "lmstudio"}, @@ -237,6 +238,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 9fc7007e9..dd2e1de3f 100644 --- a/web/README.md +++ b/web/README.md @@ -357,7 +357,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 98bd501f5..a34ff8ebd 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 c08b3bdd6..3984cb7dd 100644 --- a/web/frontend/src/components/models/models-page.tsx +++ b/web/frontend/src/components/models/models-page.tsx @@ -30,9 +30,10 @@ const PROVIDER_PRIORITY: Record = { shengsuanyun: 14, ollama: 15, vllm: 16, - mistral: 17, - avian: 18, - mimo: 19, + ovms: 17, + mistral: 18, + avian: 19, + mimo: 20, } interface ProviderGroup { diff --git a/web/frontend/src/components/models/provider-icon.tsx b/web/frontend/src/components/models/provider-icon.tsx index 814a59834..860bb4b03 100644 --- a/web/frontend/src/components/models/provider-icon.tsx +++ b/web/frontend/src/components/models/provider-icon.tsx @@ -36,6 +36,7 @@ const PROVIDER_DOMAINS: Record = { mistral: "mistral.ai", avian: "avian.io", vllm: "vllm.ai", + ovms: "docs.openvino.ai", zhipu: "zhipuai.cn", mimo: "xiaomi.com", } diff --git a/web/frontend/src/components/models/provider-label.ts b/web/frontend/src/components/models/provider-label.ts index 82600a96f..28666b798 100644 --- a/web/frontend/src/components/models/provider-label.ts +++ b/web/frontend/src/components/models/provider-label.ts @@ -17,6 +17,7 @@ const PROVIDER_LABELS: Record = { mistral: "Mistral AI", avian: "Avian", vllm: "VLLM (local)", + ovms: "OVMS (local)", zhipu: "Zhipu AI (智谱)", mimo: "Xiaomi MiMo", }