diff --git a/README.md b/README.md index dbf079c..2d55d54 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@

ShellOracle is an innovative terminal utility designed for intelligent shell command generation, bringing a new level of -efficiency to your command-line interactions. ShellOracle currently supports Ollama, OpenAI, Deepseek, LocalAI, and Grok! +efficiency to your command-line interactions. ShellOracle currently supports Ollama, llmman, OpenAI, Deepseek, LocalAI, and Grok! ![ShellOracle](https://i.imgur.com/lqTW1lO.gif) @@ -93,6 +93,16 @@ ollama pull qwen2.5-coder Refer to the [Ollama docs](https://ollama.ai) for installation, available models, and usage. +### llmman + +[llmman](https://github.com/llmmanorg/llmman) serves the Ollama API on port 17434. Start it and pull the model you +chose in the configure step. For example, if you chose `gemma4`, run: + +```shell +llmman serve +llmman pull gemma4 +``` + ### OpenAI To use ShellOracle with OpenAI's models, create an [API key](https://platform.openai.com/account/api-keys). Edit diff --git a/src/shelloracle/providers/__init__.py b/src/shelloracle/providers/__init__.py index 57e446d..ee1aa54 100644 --- a/src/shelloracle/providers/__init__.py +++ b/src/shelloracle/providers/__init__.py @@ -84,6 +84,7 @@ def __get__(self, instance: Provider, owner: type[Provider]) -> T: def _providers() -> dict[str, type[Provider]]: from shelloracle.providers.deepseek import Deepseek from shelloracle.providers.google import Google + from shelloracle.providers.llmman import Llmman from shelloracle.providers.localai import LocalAI from shelloracle.providers.ollama import Ollama from shelloracle.providers.openai import OpenAI @@ -92,6 +93,7 @@ def _providers() -> dict[str, type[Provider]]: return { Ollama.name: Ollama, + Llmman.name: Llmman, OpenAI.name: OpenAI, OpenAICompat.name: OpenAICompat, LocalAI.name: LocalAI, diff --git a/src/shelloracle/providers/llmman.py b/src/shelloracle/providers/llmman.py new file mode 100644 index 0000000..f0df8de --- /dev/null +++ b/src/shelloracle/providers/llmman.py @@ -0,0 +1,13 @@ +from __future__ import annotations + +from shelloracle.providers import Setting +from shelloracle.providers.ollama import Ollama + + +class Llmman(Ollama): + """llmman (https://github.com/llmmanorg/llmman) serves the Ollama API on port 17434.""" + + name = "llmman" + + port = Setting(default=17434) + model = Setting(default="gemma4") diff --git a/src/shelloracle/providers/ollama.py b/src/shelloracle/providers/ollama.py index 8fbc752..fbc83b9 100644 --- a/src/shelloracle/providers/ollama.py +++ b/src/shelloracle/providers/ollama.py @@ -77,5 +77,5 @@ async def generate(self, prompt: str) -> AsyncIterator[str]: raise ProviderError(response["error"]) yield response["response"] except (httpx.HTTPError, httpx.StreamError) as e: - msg = f"Something went wrong while querying Ollama: {e}" + msg = f"Something went wrong while querying {self.name}: {e}" raise ProviderError(msg) from e diff --git a/tests/providers/test_llmman.py b/tests/providers/test_llmman.py new file mode 100644 index 0000000..c9e5239 --- /dev/null +++ b/tests/providers/test_llmman.py @@ -0,0 +1,54 @@ +import pytest +from pytest_httpx import IteratorStream + +from shelloracle.config import Configuration +from shelloracle.providers.llmman import Llmman + + +class TestLlmman: + @pytest.fixture + def llmman_config(self): + config = { + "shelloracle": {"provider": "llmman"}, + "provider": {"llmman": {"host": "localhost", "port": 17434, "model": "gemma4"}}, + } + return Configuration(config) + + @pytest.fixture + def llmman_instance(self, llmman_config): + return Llmman(llmman_config) + + def test_name(self): + assert Llmman.name == "llmman" + + def test_host(self, llmman_instance): + assert llmman_instance.host == "localhost" + + def test_port(self, llmman_instance): + assert llmman_instance.port == 17434 + + def test_model(self, llmman_instance): + assert llmman_instance.model == "gemma4" + + def test_endpoint(self, llmman_instance): + assert llmman_instance.endpoint == "http://localhost:17434/api/generate" + + def test_defaults(self): + instance = Llmman(Configuration({"shelloracle": {"provider": "llmman"}, "provider": {}})) + assert instance.endpoint == "http://localhost:17434/api/generate" + assert instance.model == "gemma4" + + @pytest.mark.asyncio + async def test_generate(self, llmman_instance, httpx_mock): + responses = [ + b'{"response": "cat"}\n', + b'{"response": " test"}\n', + b'{"response": "."}\n', + b'{"response": "py"}\n', + b'{"response": ""}\n', + ] + httpx_mock.add_response(stream=IteratorStream(responses)) + result = "" + async for response in llmman_instance.generate(""): + result += response + assert result == "cat test.py"