diff --git a/backend/.env.sample b/backend/.env.sample index 1b356257..7c2ad786 100644 --- a/backend/.env.sample +++ b/backend/.env.sample @@ -17,6 +17,9 @@ SQLITE_DB_PATH= # LLM Integrations OPENAI_API_KEY= +EVOLINK_API_KEY= +EVOLINK_API_BASE=https://direct.evolink.ai/v1 +EVOLINK_CHAT_MODEL=gpt-5.2 ANTHROPIC_API_KEY= GOOGLEAI_API_KEY= @@ -60,4 +63,4 @@ SERP_API_KEY= # Devzery API Test DEVZERY_API_KEY= -DEVZERY_SOURCE_NAME= \ No newline at end of file +DEVZERY_SOURCE_NAME= diff --git a/backend/README.md b/backend/README.md index 2bbb3f47..6d07d275 100644 --- a/backend/README.md +++ b/backend/README.md @@ -61,7 +61,7 @@ The `VideoDBTool` (`director/tools/videodb_tool.py`) provides an interface for i ## 🧠 LLM Integration -The `BaseLLM` class (`director/llm/base.py`) provides an abstraction for integrating different Language Model providers. The current implementation supports OpenAI (`director/llm/openai.py`). +The `BaseLLM` class (`director/llm/base.py`) provides an abstraction for integrating different Language Model providers. The current implementation supports OpenAI (`director/llm/openai.py`) and EvoLink (`director/llm/evolink.py`). ## 📦 Dependencies @@ -123,4 +123,3 @@ make run ## 📚 Further Documentation For more detailed information about specific components, please refer to the MkDocs documentation. - diff --git a/backend/director/constants.py b/backend/director/constants.py index a398d134..b9350666 100644 --- a/backend/director/constants.py +++ b/backend/director/constants.py @@ -18,6 +18,7 @@ class LLMType(str, Enum): """Enum for LLM types""" OPENAI = "openai" + EVOLINK = "evolink" ANTHROPIC = "anthropic" GOOGLEAI = "googleai" VIDEODB_PROXY = "videodb_proxy" @@ -27,7 +28,9 @@ class EnvPrefix(str, Enum): """Enum for environment prefixes""" OPENAI_ = "OPENAI_" + EVOLINK_ = "EVOLINK_" ANTHROPIC_ = "ANTHROPIC_" GOOGLEAI_ = "GOOGLEAI_" -DOWNLOADS_PATH="director/downloads" + +DOWNLOADS_PATH = "director/downloads" diff --git a/backend/director/llm/__init__.py b/backend/director/llm/__init__.py index 71e79c4c..f538c9aa 100644 --- a/backend/director/llm/__init__.py +++ b/backend/director/llm/__init__.py @@ -3,6 +3,7 @@ from director.constants import LLMType from director.llm.openai import OpenAI +from director.llm.evolink import Evolink from director.llm.anthropic import AnthropicAI from director.llm.googleai import GoogleAI from director.llm.videodb_proxy import VideoDBProxy @@ -12,13 +13,18 @@ def get_default_llm(): """Get default LLM""" openai = True if os.getenv("OPENAI_API_KEY") else False + evolink = True if os.getenv("EVOLINK_API_KEY") else False anthropic = True if os.getenv("ANTHROPIC_API_KEY") else False googleai = True if os.getenv("GOOGLEAI_API_KEY") else False default_llm = os.getenv("DEFAULT_LLM") - if openai or default_llm == LLMType.OPENAI: + if default_llm == LLMType.EVOLINK: + return Evolink() + elif openai or default_llm == LLMType.OPENAI: return OpenAI() + elif evolink: + return Evolink() elif anthropic or default_llm == LLMType.ANTHROPIC: return AnthropicAI() elif googleai or default_llm == LLMType.GOOGLEAI: diff --git a/backend/director/llm/evolink.py b/backend/director/llm/evolink.py new file mode 100644 index 00000000..abe00b67 --- /dev/null +++ b/backend/director/llm/evolink.py @@ -0,0 +1,52 @@ +from enum import Enum + +from pydantic import Field, FieldValidationInfo, field_validator +from pydantic_settings import SettingsConfigDict + +from director.constants import EnvPrefix, LLMType +from director.llm.openai import OpenAI, OpenaiConfig + + +class EvolinkChatModel(str, Enum): + """Enum for EvoLink chat models.""" + + GPT5_2 = "gpt-5.2" + GPT5_1 = "gpt-5.1" + GEMINI_3_1_PRO = "gemini-3.1-pro" + DEEPSEEK_V4_PRO = "deepseek-v4-pro" + DOUBAO_SEED_2_0_PRO = "doubao-seed-2.0-pro" + + +class EvolinkConfig(OpenaiConfig): + """EvoLink Config.""" + + model_config = SettingsConfigDict( + env_prefix=EnvPrefix.EVOLINK_, + extra="ignore", + ) + + llm_type: str = LLMType.EVOLINK + api_key: str = "" + api_base: str = "https://direct.evolink.ai/v1" + chat_model: str = Field(default=EvolinkChatModel.GPT5_2) + + @field_validator("api_key") + @classmethod + def validate_non_empty(cls, v, info: FieldValidationInfo): + if not v: + raise ValueError( + f"{info.field_name} must not be empty. Please set {EnvPrefix.EVOLINK_.value}{info.field_name.upper()} environment variable." + ) + return v + + +class Evolink(OpenAI): + """EvoLink LLM integration using the OpenAI-compatible Chat Completions API.""" + + def __init__(self, config: EvolinkConfig = None): + """ + :param config: EvoLink Config + """ + if config is None: + config = EvolinkConfig() + super().__init__(config=config) diff --git a/backend/tests/llm/test_evolink.py b/backend/tests/llm/test_evolink.py new file mode 100644 index 00000000..fc6cada5 --- /dev/null +++ b/backend/tests/llm/test_evolink.py @@ -0,0 +1,42 @@ +import pytest + +from director.constants import LLMType +from director.llm.evolink import Evolink, EvolinkConfig, EvolinkChatModel + + +def test_evolink_config_uses_direct_api_defaults(monkeypatch): + monkeypatch.setenv("EVOLINK_API_KEY", "test-key") + + config = EvolinkConfig() + + assert config.llm_type == LLMType.EVOLINK + assert config.api_key == "test-key" + assert config.api_base == "https://direct.evolink.ai/v1" + assert config.chat_model == EvolinkChatModel.GPT5_2 + + +def test_evolink_config_supports_env_overrides(monkeypatch): + monkeypatch.setenv("EVOLINK_API_KEY", "test-key") + monkeypatch.setenv("EVOLINK_API_BASE", "https://example.com/v1") + monkeypatch.setenv("EVOLINK_CHAT_MODEL", "deepseek-v4-pro") + + config = EvolinkConfig() + + assert config.api_base == "https://example.com/v1" + assert config.chat_model == "deepseek-v4-pro" + + +def test_evolink_config_requires_api_key(monkeypatch): + monkeypatch.delenv("EVOLINK_API_KEY", raising=False) + + with pytest.raises(ValueError, match="EVOLINK_API_KEY"): + EvolinkConfig() + + +def test_default_llm_can_select_evolink(monkeypatch): + import director.llm as llm_module + + monkeypatch.setenv("DEFAULT_LLM", LLMType.EVOLINK) + monkeypatch.setenv("EVOLINK_API_KEY", "test-key") + + assert isinstance(llm_module.get_default_llm(), Evolink) diff --git a/docs/llm/evolink.md b/docs/llm/evolink.md new file mode 100644 index 00000000..1c00c38a --- /dev/null +++ b/docs/llm/evolink.md @@ -0,0 +1,17 @@ +## EvoLink + +EvoLink extends the OpenAI LLM integration and uses EvoLink's OpenAI-compatible Chat Completions API. + +Set `EVOLINK_API_KEY` to your EvoLink API key. The default API base is `https://direct.evolink.ai/v1`, and the default chat model is `gpt-5.2`. + +### EvoLink Config + +EvoLink Config is the configuration object for EvoLink. It is used to configure EvoLink and is passed to EvoLink when it is created. + +::: director.llm.evolink.EvolinkConfig + +### EvoLink Interface + +EvoLink is the LLM used by the agents and tools. It is used to generate responses to messages. + +::: director.llm.evolink.Evolink diff --git a/mkdocs.yml b/mkdocs.yml index d7ef08b1..8da59f6e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -73,6 +73,7 @@ nav: - 'Interface': 'llm/interface.md' - Integrations: - 'OpenAI': 'llm/openai.md' + - 'EvoLink': 'llm/evolink.md' - 'AnthropicAI': 'llm/anthropic.md' - 'GoogleAI': 'llm/googleai.md' - 'Database':