diff --git a/README.md b/README.md index f423d467..0ff6655f 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,7 @@ for fast retrieval and self-evolving reuse. | Capability | Provider | Used for | Fill these `.env` slots | | --- | --- | --- | --- | | Chat + multimodal | [OpenRouter](https://openrouter.ai/) | `LLM` / `MULTIMODAL` | `EVEROS_LLM__API_KEY`, `EVEROS_MULTIMODAL__API_KEY` | +| Chat | Atlas Cloud OpenAI-compatible endpoint | `LLM` | `EVEROS_LLM__PROVIDER=atlascloud`, `EVEROS_LLM__API_KEY` | | Embedding + rerank | [DeepInfra](https://deepinfra.com/) | `EMBEDDING` / `RERANK` | `EVEROS_EMBEDDING__API_KEY`, `EVEROS_RERANK__API_KEY` | You can use other OpenAI-compatible providers by changing the matching diff --git a/config.example.toml b/config.example.toml index f8043127..d7fda781 100644 --- a/config.example.toml +++ b/config.example.toml @@ -21,10 +21,17 @@ # ── LLM ─────────────────────────────────────────────── # OpenAI-protocol chat-completions endpoint used by the algo extractors. [llm] +provider = "openai" model = "gpt-4.1-mini" api_key = "sk-..." base_url = "https://api.openai.com/v1" +# Atlas Cloud is also supported through its OpenAI-compatible endpoint: +# provider = "atlascloud" +# model = "qwen/qwen3.5-flash" +# api_key = "..." +# base_url = "https://api.atlascloud.ai/v1" + # ── Embedding ───────────────────────────────────────── [embedding] model = "Qwen/Qwen3-Embedding-4B" diff --git a/docs/configuration.md b/docs/configuration.md index 98fdf347..a62aef34 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -103,6 +103,7 @@ everos init --root /data/everos | Field | Type | Default | Required | Description | |---|---|---|---|---| +| `provider` | string | `"openrouter"` | No | Provider preset. Use `"atlascloud"` to apply Atlas Cloud's OpenAI-compatible defaults. | | `model` | string | `"gpt-4.1-mini"` | No | LLM model identifier. | | `api_key` | string | — | **Yes** | API key for the LLM provider. | | `base_url` | string | — | No | Custom endpoint URL (OpenAI-compatible). | @@ -202,8 +203,9 @@ variables directly: ```dockerfile ENV EVEROS_ROOT=/data/everos +ENV EVEROS_LLM__PROVIDER=atlascloud ENV EVEROS_LLM__API_KEY=sk-... -ENV EVEROS_LLM__MODEL=gpt-4o +ENV EVEROS_LLM__MODEL=qwen/qwen3.5-flash ENV EVEROS_EMBEDDING__MODEL=text-embedding-3-large ENV EVEROS_EMBEDDING__API_KEY=sk-... ENV EVEROS_EMBEDDING__BASE_URL=https://api.openai.com/v1 @@ -225,6 +227,7 @@ Examples: | TOML | Environment Variable | |---|---| +| `[llm] provider = "atlascloud"` | `EVEROS_LLM__PROVIDER=atlascloud` | | `[llm] api_key = "sk-..."` | `EVEROS_LLM__API_KEY=sk-...` | | `[sqlite] busy_timeout_ms = 10000` | `EVEROS_SQLITE__BUSY_TIMEOUT_MS=10000` | | `[memory] timezone = "Asia/Tokyo"` | `EVEROS_MEMORY__TIMEZONE=Asia/Tokyo` | diff --git a/src/everos/config/default.toml b/src/everos/config/default.toml index 539e68e1..c4c3691a 100644 --- a/src/everos/config/default.toml +++ b/src/everos/config/default.toml @@ -52,8 +52,10 @@ cache_size_kb = 2048 [llm] # Provider-agnostic OpenAI-protocol client config. Override via env: -# EVEROS_LLM__MODEL, EVEROS_LLM__API_KEY, EVEROS_LLM__BASE_URL +# EVEROS_LLM__PROVIDER, EVEROS_LLM__MODEL, EVEROS_LLM__API_KEY, +# EVEROS_LLM__BASE_URL # Or set the field directly in this file (/everos.toml). +provider = "openrouter" model = "openai/gpt-4.1-mini" api_key = "" base_url = "https://openrouter.ai/api/v1" diff --git a/src/everos/config/settings.py b/src/everos/config/settings.py index 354d6c54..691539ca 100644 --- a/src/everos/config/settings.py +++ b/src/everos/config/settings.py @@ -28,7 +28,14 @@ from typing import Literal from zoneinfo import ZoneInfo, ZoneInfoNotFoundError -from pydantic import BaseModel, ConfigDict, Field, SecretStr, field_validator +from pydantic import ( + BaseModel, + ConfigDict, + Field, + SecretStr, + field_validator, + model_validator, +) from pydantic_settings import ( BaseSettings, PydanticBaseSettingsSource, @@ -38,6 +45,9 @@ _DEFAULT_TOML_PATH = Path(__file__).parent / "default.toml" _DEFAULT_ROOT = Path("~/.everos") +_ATLASCLOUD_BASE_URL = "https://api.atlascloud.ai/v1" +_ATLASCLOUD_DEFAULT_MODEL = "qwen/qwen3.5-flash" +_OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1" def resolve_root(explicit: str | None = None) -> Path: @@ -124,15 +134,38 @@ class LLMSettings(BaseModel): endpoint plugs in via ``base_url``. Env binding (via parent ``Settings``): + EVEROS_LLM__PROVIDER EVEROS_LLM__MODEL EVEROS_LLM__API_KEY EVEROS_LLM__BASE_URL """ + provider: Literal["openrouter", "openai", "atlascloud", "custom"] = "openrouter" model: str = "gpt-4.1-mini" api_key: SecretStr | None = None base_url: str | None = None + @model_validator(mode="after") + def _apply_provider_defaults(self) -> LLMSettings: + """Apply provider-specific OpenAI-compatible defaults.""" + if self.provider != "atlascloud": + return self + + if not self.base_url or self.base_url == _OPENROUTER_BASE_URL: + self.base_url = _ATLASCLOUD_BASE_URL + + if self.model in {"openai/gpt-4.1-mini", "gpt-4.1-mini"}: + self.model = _ATLASCLOUD_DEFAULT_MODEL + + if not self.api_key or not self.api_key.get_secret_value(): + atlas_key = os.environ.get("ATLASCLOUD_API_KEY") or os.environ.get( + "ATLAS_CLOUD_API_KEY" + ) + if atlas_key: + self.api_key = SecretStr(atlas_key) + + return self + class MultimodalSettings(BaseModel): """Multimodal parsing LLM config (everalgo-parser). diff --git a/src/everos/templates/env.template b/src/everos/templates/env.template old mode 100755 new mode 100644 index ad02bee0..1c926fa9 --- a/src/everos/templates/env.template +++ b/src/everos/templates/env.template @@ -22,10 +22,11 @@ # ─── LLM (OpenAI-protocol compatible) ──────────────── # Any OpenAI-API-compatible endpoint plugs in via base_url. Defaults -# below target OpenRouter (one key, broad model catalogue); switch to -# OpenAI, vLLM, Ollama (OpenAI bridge), or any other compatible endpoint -# by changing model + base_url + api_key. +# below target OpenRouter (one key, broad model catalogue). Set +# EVEROS_LLM__PROVIDER=atlascloud to use Atlas Cloud's preset endpoint +# and model defaults. +EVEROS_LLM__PROVIDER=openrouter EVEROS_LLM__MODEL=openai/gpt-4.1-mini EVEROS_LLM__API_KEY= EVEROS_LLM__BASE_URL=https://openrouter.ai/api/v1 diff --git a/tests/unit/test_config/test_settings.py b/tests/unit/test_config/test_settings.py index fb4fbb17..d37caa4a 100644 --- a/tests/unit/test_config/test_settings.py +++ b/tests/unit/test_config/test_settings.py @@ -118,6 +118,34 @@ def test_embedding_rerank_defaults() -> None: assert s.rerank.api_key.get_secret_value() == "" assert s.rerank.timeout_seconds == 30.0 assert s.llm.api_key.get_secret_value() == "" + assert s.llm.provider == "openrouter" + + +def test_atlascloud_llm_provider_defaults(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("ATLASCLOUD_API_KEY", "atlas-key") + s = Settings(llm={"provider": "atlascloud"}) + + assert s.llm.provider == "atlascloud" + assert s.llm.model == "qwen/qwen3.5-flash" + assert s.llm.base_url == "https://api.atlascloud.ai/v1" + assert s.llm.api_key is not None + assert s.llm.api_key.get_secret_value() == "atlas-key" + + +def test_atlascloud_llm_provider_preserves_explicit_values() -> None: + s = Settings( + llm={ + "provider": "atlascloud", + "model": "deepseek-ai/deepseek-v4-pro", + "api_key": "custom-key", + "base_url": "https://proxy.example.test/v1", + } + ) + + assert s.llm.model == "deepseek-ai/deepseek-v4-pro" + assert s.llm.base_url == "https://proxy.example.test/v1" + assert s.llm.api_key is not None + assert s.llm.api_key.get_secret_value() == "custom-key" def test_resolve_root_default(monkeypatch: pytest.MonkeyPatch) -> None: