Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 42 additions & 20 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,25 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.13"

- name: Install uv
run: curl -LsSf https://astral.sh/uv/install.sh | sh
- name: Sync dependencies
uses: astral-sh/setup-uv@v7

- name: Set up Python
run: uv python install 3.13

- name: Install dependencies
working-directory: backend
run: uv sync
run: uv sync --all-extras

- name: Check formatting
working-directory: backend
run: uv run ruff format --check .

- name: Lint
working-directory: backend
run: uv run ruff check .

frontend-format:
runs-on: ubuntu-latest
strategy:
Expand All @@ -31,15 +37,19 @@ jobs:
- e2e_tests/tests_omni_light
steps:
- uses: actions/checkout@v6

- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: "20"

- name: Install pnpm
run: npm install -g pnpm

- name: Install dependencies
working-directory: ${{ matrix.working-dir }}
run: pnpm install

- name: Check formatting and linting
working-directory: ${{ matrix.working-dir }}
run: pnpm check
Expand All @@ -48,32 +58,38 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.13"

- name: Install uv
run: curl -LsSf https://astral.sh/uv/install.sh | sh
- name: Sync dependencies
uses: astral-sh/setup-uv@v7

- name: Set up Python
run: uv python install 3.13

- name: Install dependencies
working-directory: backend
run: uv sync
run: uv sync --all-extras

- name: Run tests
working-directory: backend
run: uv run pytest
run: uv run pytest -v

frontend-unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: "20"

- name: Install pnpm
run: npm install -g pnpm

- name: Install dependencies
working-directory: frontend_omni
run: pnpm install

- name: Run unit tests
working-directory: frontend_omni
run: pnpm test
Expand All @@ -87,27 +103,33 @@ jobs:
- e2e_tests/tests_omni_light
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.13"

- name: Install uv
run: curl -LsSf https://astral.sh/uv/install.sh | sh
uses: astral-sh/setup-uv@v7

- name: Set up Python
run: uv python install 3.13

- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: "20"

- name: Install pnpm
run: npm install -g pnpm

- name: Install dependencies for frontend
working-directory: frontend_omni
run: pnpm install

- name: Install dependencies for tests
working-directory: ${{ matrix.working-dir }}
run: pnpm install

- name: Install Playwright browsers
working-directory: ${{ matrix.working-dir }}
run: pnpm playwright install --with-deps

- name: Run Playwright tests
working-directory: ${{ matrix.working-dir }}
run: pnpm test
5 changes: 4 additions & 1 deletion backend/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [0.0.1] - 2026-02-12

### Added

- ModAI inital Backend
19 changes: 11 additions & 8 deletions backend/src/modai/default_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,21 @@ modules:
chat_openai: chat_openai
chat_openai:
class: modai.modules.chat.openai_llm_chat.OpenAILLMChatModule
config:
openai_client:
api_key: ${OPENAI_API_KEY}
llm_provider_store:
class: modai.modules.llm_provider_store.sql_model_llm_provider_store.SQLAlchemyLLMProviderStore
module_dependencies:
llm_provider_module: openai_model_provider
model_provider_store:
class: modai.modules.model_provider_store.sql_model_provider_store.SQLAlchemyModelProviderStore
config:
database_url: "sqlite:///./llm.db"
echo: false
openai_llm_provider:
class: modai.modules.llm_provider.openai_provider.OpenAIProviderModule
openai_model_provider:
class: modai.modules.model_provider.openai_provider.OpenAIProviderModule
module_dependencies:
llm_provider_store: "model_provider_store"
central_model_provider_router:
class: modai.modules.model_provider.central_router.CentralModelProviderRouter
module_dependencies:
llm_provider_store: "llm_provider_store"
openai_provider: "openai_model_provider"
user_store:
class: modai.modules.user_store.sql_model_user_store.SQLAlchemyUserStore
config:
Expand Down
2 changes: 1 addition & 1 deletion backend/src/modai/modules/chat/web_chat_router.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from fastapi import APIRouter, Request, Body
from fastapi import Request, Body
from fastapi.responses import StreamingResponse, JSONResponse
from typing import Any, Dict, cast
from modai.module import ModuleDependencies
Expand Down
1 change: 0 additions & 1 deletion backend/src/modai/modules/model_provider/central_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from modai.modules.model_provider.module import (
ModelProviderResponse,
ModelProviderModule,
ModelResponse,
Model,
)

Expand Down
2 changes: 1 addition & 1 deletion backend/src/modai/modules/session/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import logging
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Any, Dict
from typing import Any

from fastapi import Request, Response
from modai.module import ModaiModule, ModuleDependencies
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
This implementation delegates persistence to a UserSettingsStore module.
"""

from typing import Any, Dict
from typing import Any
from fastapi import Request, HTTPException

from modai.module import ModuleDependencies
Expand Down
2 changes: 1 addition & 1 deletion backend/tests/abstract_model_provider_store_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ async def test_update_provider_with_duplicate_name_raises_exception(
self, model_provider_store
):
"""Test that updating a provider to use an existing name raises an exception"""
provider1 = await model_provider_store.add_provider(
await model_provider_store.add_provider(
name="Provider1", url="https://api1.com", properties={}
)
provider2 = await model_provider_store.add_provider(
Expand Down
4 changes: 2 additions & 2 deletions backend/tests/test_authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
import pytest
from unittest.mock import Mock, MagicMock, AsyncMock
from fastapi.testclient import TestClient
from fastapi import FastAPI, Request, Response
from fastapi import FastAPI

sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from modai.module import ModuleDependencies
from modai.modules.authentication.password_authentication_module import (
PasswordAuthenticationModule,
)
from modai.modules.session.module import SessionModule, Session
from modai.modules.session.module import SessionModule
from modai.modules.user_store.module import UserStore, User, UserCredentials


Expand Down
2 changes: 0 additions & 2 deletions backend/tests/test_central_model_provider_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
import sys
import os
import pytest
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock
from fastapi.testclient import TestClient
from fastapi import FastAPI

Expand Down
13 changes: 6 additions & 7 deletions backend/tests/test_chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import pytest_asyncio
from openai import AsyncOpenAI
from unittest.mock import Mock, AsyncMock
from typing import AsyncGenerator

sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from modai.module import ModuleDependencies
Expand Down Expand Up @@ -70,7 +69,7 @@ async def openai_client(request):
async def test_llm_generate_response():
"""Test LLM generate_response method directly."""
from fastapi import Request
from unittest.mock import Mock, AsyncMock
from unittest.mock import Mock
from modai.modules.model_provider.module import (
ModelProvidersListResponse,
ModelProviderResponse,
Expand Down Expand Up @@ -139,7 +138,7 @@ async def test_llm_generate_response():
async def test_llm_generate_response_streaming():
"""Test LLM generate_response method directly for streaming."""
from fastapi import Request
from unittest.mock import Mock, AsyncMock
from unittest.mock import Mock
from modai.modules.model_provider.module import (
ModelProvidersListResponse,
ModelProviderResponse,
Expand Down Expand Up @@ -210,7 +209,7 @@ async def test_llm_generate_response_streaming():
async def test_chat_responses_api(openai_client: AsyncOpenAI, request):
"""Test chat responses API."""

client_type = request.node.callspec.params["openai_client"]
request.node.callspec.params["openai_client"]
model = "gpt-4o" # No backend_proxy

# Make the request
Expand Down Expand Up @@ -242,7 +241,7 @@ async def test_chat_responses_api(openai_client: AsyncOpenAI, request):
async def test_chat_responses_api_streaming(openai_client: AsyncOpenAI, request):
"""Test streaming chat responses API."""

client_type = request.node.callspec.params["openai_client"]
request.node.callspec.params["openai_client"]
model = "gpt-4o" # No backend_proxy

# Make the streaming request
Expand Down Expand Up @@ -351,7 +350,7 @@ async def test_chat_web_module_routing_streaming():
async def test_openai_llm_invalid_model_format():
"""Test OpenAILLMChatModule with invalid model format."""
from fastapi import Request
from unittest.mock import Mock, AsyncMock
from unittest.mock import Mock
from modai.modules.model_provider.module import (
ModelProvidersListResponse,
ModelProviderResponse,
Expand Down Expand Up @@ -403,7 +402,7 @@ async def test_openai_llm_invalid_model_format():
async def test_openai_llm_provider_not_found():
"""Test OpenAILLMChatModule when provider is not found."""
from fastapi import Request
from unittest.mock import Mock, AsyncMock
from unittest.mock import Mock
from modai.modules.model_provider.module import ModelProvidersListResponse

# Mock provider module with no providers
Expand Down
2 changes: 1 addition & 1 deletion backend/tests/test_config_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def test_config_loader_valid_config(tmp_path: Path):
loader = YamlConfigModule(ModuleDependencies(), {"config_path": str(config_file)})
config = loader.get_config()

assert config["modules"]["health"]["enabled"] == True
assert config["modules"]["health"]["enabled"]


def test_config_loader_invalid_yaml(tmp_path: Path):
Expand Down
3 changes: 1 addition & 2 deletions backend/tests/test_model_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@
import pytest
from pathlib import Path
from dotenv import find_dotenv, load_dotenv
from unittest.mock import AsyncMock, MagicMock
from unittest.mock import AsyncMock
from fastapi.testclient import TestClient
from fastapi import FastAPI

sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))

from modai.modules.model_provider.module import ModelProviderModule
from modai.modules.model_provider.openai_provider import OpenAIProviderModule
from modai.modules.model_provider_store.module import ModelProvider, ModelProviderStore
from modai.module import ModuleDependencies
Expand Down
17 changes: 8 additions & 9 deletions backend/tests/test_user_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
"""

import pytest
from unittest.mock import AsyncMock, MagicMock
from unittest.mock import MagicMock
from fastapi import HTTPException, Request
from typing import Dict, Any
from typing import Any

from modai.module import ModuleDependencies
from modai.modules.user_settings.module import (
Expand All @@ -20,7 +20,6 @@
SimpleUserSettingsModule,
)
from modai.modules.session.module import SessionModule
from modai.modules.user_settings_store.module import UserSettingsStore
from modai.modules.user_settings_store.inmemory_user_settings_store import (
InMemoryUserSettingsStore,
)
Expand Down Expand Up @@ -186,7 +185,7 @@ async def test_update_user_settings_new_user(
assert isinstance(result, UserSettingsResponse)
assert result.settings["theme"]["mode"] == "dark"
assert result.settings["theme"]["primary_color"] == "#1976d2"
assert result.settings["notifications"]["email_enabled"] == True
assert result.settings["notifications"]["email_enabled"]

@pytest.mark.asyncio
async def test_update_user_settings_existing_user(
Expand Down Expand Up @@ -219,8 +218,8 @@ async def test_update_user_settings_existing_user(
assert result.settings["theme"]["mode"] == "dark"
assert result.settings["theme"]["primary_color"] == "#red"
# Notifications should remain unchanged
assert result.settings["notifications"]["email_enabled"] == False
assert result.settings["notifications"]["push_enabled"] == True
assert not result.settings["notifications"]["email_enabled"]
assert result.settings["notifications"]["push_enabled"]

@pytest.mark.asyncio
async def test_update_user_settings_unauthorized_modification(
Expand Down Expand Up @@ -419,7 +418,7 @@ async def test_update_user_setting_type_existing_user(

# Verify other settings are preserved
full_settings = await module.get_user_settings(user_id, mock_request)
assert full_settings.settings["notifications"]["email_enabled"] == False
assert not full_settings.settings["notifications"]["email_enabled"]

@pytest.mark.asyncio
async def test_update_user_setting_type_unauthorized_modification(
Expand Down Expand Up @@ -470,6 +469,6 @@ async def test_setting_type_flow_integration(

assert isinstance(get_result, UserSettingTypeResponse)
assert get_result.settings == update_result.settings
assert get_result.settings["email_enabled"] == True
assert get_result.settings["push_enabled"] == False
assert get_result.settings["email_enabled"]
assert not get_result.settings["push_enabled"]
assert get_result.settings["frequency"] == "daily"
Loading