refactor: reorganize folder structure for better maintainability
Changes: - Move migrations/ under open_notebook/database/migrations/ - Extract AI models to open_notebook/ai/ (Model, ModelManager, provision) - Extract podcasts to open_notebook/podcasts/ (EpisodeProfile, SpeakerProfile, PodcastEpisode) - Reorganize prompts to mirror graphs structure (chat/, source_chat/) This improves code organization by: - Consolidating database concerns (migrations now with database code) - Separating AI infrastructure from domain entities - Isolating podcast feature into its own module - Creating consistent prompt/graph naming conventions All 52 tests pass.
This commit is contained in:
parent
93cda6c42a
commit
ab5560c9a2
48 changed files with 50 additions and 47 deletions
|
|
@ -7,7 +7,7 @@ from typing import List
|
|||
from loguru import logger
|
||||
|
||||
from api.client import api_client
|
||||
from open_notebook.domain.podcast import EpisodeProfile
|
||||
from open_notebook.podcasts.models import EpisodeProfile
|
||||
|
||||
|
||||
class EpisodeProfilesService:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
# Load environment variables
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
from contextlib import asynccontextmanager
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from typing import List, Optional
|
|||
from loguru import logger
|
||||
|
||||
from api.client import api_client
|
||||
from open_notebook.domain.models import DefaultModels, Model
|
||||
from open_notebook.ai.models import DefaultModels, Model
|
||||
|
||||
|
||||
class ModelsService:
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from pydantic import BaseModel
|
|||
from surreal_commands import get_command_status, submit_command
|
||||
|
||||
from open_notebook.domain.notebook import Notebook
|
||||
from open_notebook.domain.podcast import EpisodeProfile, PodcastEpisode, SpeakerProfile
|
||||
from open_notebook.podcasts.models import EpisodeProfile, PodcastEpisode, SpeakerProfile
|
||||
|
||||
|
||||
class PodcastGenerationRequest(BaseModel):
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from loguru import logger
|
|||
|
||||
from api.command_service import CommandService
|
||||
from api.models import EmbedRequest, EmbedResponse
|
||||
from open_notebook.domain.models import model_manager
|
||||
from open_notebook.ai.models import model_manager
|
||||
from open_notebook.domain.notebook import Note, Source
|
||||
|
||||
router = APIRouter()
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from fastapi import APIRouter, HTTPException
|
|||
from loguru import logger
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from open_notebook.domain.podcast import EpisodeProfile
|
||||
from open_notebook.podcasts.models import EpisodeProfile
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from api.models import (
|
|||
ModelResponse,
|
||||
ProviderAvailabilityResponse,
|
||||
)
|
||||
from open_notebook.domain.models import DefaultModels, Model
|
||||
from open_notebook.ai.models import DefaultModels, Model
|
||||
from open_notebook.exceptions import InvalidInputError
|
||||
|
||||
router = APIRouter()
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from fastapi.responses import StreamingResponse
|
|||
from loguru import logger
|
||||
|
||||
from api.models import AskRequest, AskResponse, SearchRequest, SearchResponse
|
||||
from open_notebook.domain.models import Model, model_manager
|
||||
from open_notebook.ai.models import Model, model_manager
|
||||
from open_notebook.domain.notebook import text_search, vector_search
|
||||
from open_notebook.exceptions import DatabaseOperationError, InvalidInputError
|
||||
from open_notebook.graphs.ask import graph as ask_graph
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from fastapi import APIRouter, HTTPException
|
|||
from loguru import logger
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from open_notebook.domain.podcast import SpeakerProfile
|
||||
from open_notebook.podcasts.models import SpeakerProfile
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ from api.models import (
|
|||
TransformationResponse,
|
||||
TransformationUpdate,
|
||||
)
|
||||
from open_notebook.domain.models import Model
|
||||
from open_notebook.ai.models import Model
|
||||
from open_notebook.domain.transformation import DefaultPrompts, Transformation
|
||||
from open_notebook.exceptions import InvalidInputError
|
||||
from open_notebook.graphs.transformation import graph as transformation_graph
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ from loguru import logger
|
|||
from pydantic import BaseModel
|
||||
from surreal_commands import CommandInput, CommandOutput, command, submit_command
|
||||
|
||||
from open_notebook.ai.models import model_manager
|
||||
from open_notebook.database.repository import ensure_record_id, repo_query
|
||||
from open_notebook.domain.models import model_manager
|
||||
from open_notebook.domain.notebook import Note, Source, SourceInsight
|
||||
from open_notebook.utils.text_utils import split_text
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from surreal_commands import CommandInput, CommandOutput, command
|
|||
|
||||
from open_notebook.config import DATA_FOLDER
|
||||
from open_notebook.database.repository import ensure_record_id, repo_query
|
||||
from open_notebook.domain.podcast import EpisodeProfile, PodcastEpisode, SpeakerProfile
|
||||
from open_notebook.podcasts.models import EpisodeProfile, PodcastEpisode, SpeakerProfile
|
||||
|
||||
try:
|
||||
from podcast_creator import configure, create_podcast
|
||||
|
|
|
|||
2
open_notebook/ai/__init__.py
Normal file
2
open_notebook/ai/__init__.py
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# AI infrastructure module
|
||||
# Contains model configuration, provisioning, and management
|
||||
|
|
@ -2,7 +2,7 @@ from esperanto import LanguageModel
|
|||
from langchain_core.language_models.chat_models import BaseChatModel
|
||||
from loguru import logger
|
||||
|
||||
from open_notebook.domain.models import model_manager
|
||||
from open_notebook.ai.models import model_manager
|
||||
from open_notebook.utils import token_count
|
||||
|
||||
|
||||
|
|
@ -96,26 +96,26 @@ class AsyncMigrationManager:
|
|||
def __init__(self):
|
||||
"""Initialize migration manager."""
|
||||
self.up_migrations = [
|
||||
AsyncMigration.from_file("migrations/1.surrealql"),
|
||||
AsyncMigration.from_file("migrations/2.surrealql"),
|
||||
AsyncMigration.from_file("migrations/3.surrealql"),
|
||||
AsyncMigration.from_file("migrations/4.surrealql"),
|
||||
AsyncMigration.from_file("migrations/5.surrealql"),
|
||||
AsyncMigration.from_file("migrations/6.surrealql"),
|
||||
AsyncMigration.from_file("migrations/7.surrealql"),
|
||||
AsyncMigration.from_file("migrations/8.surrealql"),
|
||||
AsyncMigration.from_file("migrations/9.surrealql"),
|
||||
AsyncMigration.from_file("open_notebook/database/migrations/1.surrealql"),
|
||||
AsyncMigration.from_file("open_notebook/database/migrations/2.surrealql"),
|
||||
AsyncMigration.from_file("open_notebook/database/migrations/3.surrealql"),
|
||||
AsyncMigration.from_file("open_notebook/database/migrations/4.surrealql"),
|
||||
AsyncMigration.from_file("open_notebook/database/migrations/5.surrealql"),
|
||||
AsyncMigration.from_file("open_notebook/database/migrations/6.surrealql"),
|
||||
AsyncMigration.from_file("open_notebook/database/migrations/7.surrealql"),
|
||||
AsyncMigration.from_file("open_notebook/database/migrations/8.surrealql"),
|
||||
AsyncMigration.from_file("open_notebook/database/migrations/9.surrealql"),
|
||||
]
|
||||
self.down_migrations = [
|
||||
AsyncMigration.from_file("migrations/1_down.surrealql"),
|
||||
AsyncMigration.from_file("migrations/2_down.surrealql"),
|
||||
AsyncMigration.from_file("migrations/3_down.surrealql"),
|
||||
AsyncMigration.from_file("migrations/4_down.surrealql"),
|
||||
AsyncMigration.from_file("migrations/5_down.surrealql"),
|
||||
AsyncMigration.from_file("migrations/6_down.surrealql"),
|
||||
AsyncMigration.from_file("migrations/7_down.surrealql"),
|
||||
AsyncMigration.from_file("migrations/8_down.surrealql"),
|
||||
AsyncMigration.from_file("migrations/9_down.surrealql"),
|
||||
AsyncMigration.from_file("open_notebook/database/migrations/1_down.surrealql"),
|
||||
AsyncMigration.from_file("open_notebook/database/migrations/2_down.surrealql"),
|
||||
AsyncMigration.from_file("open_notebook/database/migrations/3_down.surrealql"),
|
||||
AsyncMigration.from_file("open_notebook/database/migrations/4_down.surrealql"),
|
||||
AsyncMigration.from_file("open_notebook/database/migrations/5_down.surrealql"),
|
||||
AsyncMigration.from_file("open_notebook/database/migrations/6_down.surrealql"),
|
||||
AsyncMigration.from_file("open_notebook/database/migrations/7_down.surrealql"),
|
||||
AsyncMigration.from_file("open_notebook/database/migrations/8_down.surrealql"),
|
||||
AsyncMigration.from_file("open_notebook/database/migrations/9_down.surrealql"),
|
||||
]
|
||||
self.runner = AsyncMigrationRunner(
|
||||
up_migrations=self.up_migrations,
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ class ObjectModel(BaseModel):
|
|||
return None
|
||||
|
||||
async def save(self) -> None:
|
||||
from open_notebook.domain.models import model_manager
|
||||
from open_notebook.ai.models import model_manager
|
||||
|
||||
try:
|
||||
self.model_validate(self.model_dump(), strict=True)
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ from pydantic import BaseModel, Field, field_validator
|
|||
from surreal_commands import submit_command
|
||||
from surrealdb import RecordID
|
||||
|
||||
from open_notebook.ai.models import model_manager
|
||||
from open_notebook.database.repository import ensure_record_id, repo_query
|
||||
from open_notebook.domain.base import ObjectModel
|
||||
from open_notebook.domain.models import model_manager
|
||||
from open_notebook.exceptions import DatabaseOperationError, InvalidInputError
|
||||
from open_notebook.utils import split_text
|
||||
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ from langgraph.types import Send
|
|||
from pydantic import BaseModel, Field
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from open_notebook.ai.provision import provision_langchain_model
|
||||
from open_notebook.domain.notebook import vector_search
|
||||
from open_notebook.graphs.utils import provision_langchain_model
|
||||
from open_notebook.utils import clean_thinking_content
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -5,16 +5,15 @@ from typing import Annotated, Optional
|
|||
from ai_prompter import Prompter
|
||||
from langchain_core.messages import AIMessage, SystemMessage
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
|
||||
from open_notebook.utils import clean_thinking_content
|
||||
from langgraph.checkpoint.sqlite import SqliteSaver
|
||||
from langgraph.graph import END, START, StateGraph
|
||||
from langgraph.graph.message import add_messages
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from open_notebook.ai.provision import provision_langchain_model
|
||||
from open_notebook.config import LANGGRAPH_CHECKPOINT_FILE
|
||||
from open_notebook.domain.notebook import Notebook
|
||||
from open_notebook.graphs.utils import provision_langchain_model
|
||||
from open_notebook.utils import clean_thinking_content
|
||||
|
||||
|
||||
class ThreadState(TypedDict):
|
||||
|
|
@ -26,7 +25,7 @@ class ThreadState(TypedDict):
|
|||
|
||||
|
||||
def call_model_with_messages(state: ThreadState, config: RunnableConfig) -> dict:
|
||||
system_prompt = Prompter(prompt_template="chat").render(data=state) # type: ignore[arg-type]
|
||||
system_prompt = Prompter(prompt_template="chat/system").render(data=state) # type: ignore[arg-type]
|
||||
payload = [SystemMessage(content=system_prompt)] + state.get("messages", [])
|
||||
model_id = config.get("configurable", {}).get("model_id") or state.get(
|
||||
"model_override"
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from langchain_core.runnables import RunnableConfig
|
|||
from langgraph.graph import END, START, StateGraph
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from open_notebook.graphs.utils import provision_langchain_model
|
||||
from open_notebook.ai.provision import provision_langchain_model
|
||||
|
||||
|
||||
class PatternChainState(TypedDict):
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ from langgraph.types import Send
|
|||
from loguru import logger
|
||||
from typing_extensions import Annotated, TypedDict
|
||||
|
||||
from open_notebook.ai.models import Model, ModelManager
|
||||
from open_notebook.domain.content_settings import ContentSettings
|
||||
from open_notebook.domain.models import Model, ModelManager
|
||||
from open_notebook.domain.notebook import Asset, Source
|
||||
from open_notebook.domain.transformation import Transformation
|
||||
from open_notebook.graphs.transformation import graph as transform_graph
|
||||
|
|
|
|||
|
|
@ -5,16 +5,15 @@ from typing import Annotated, Dict, List, Optional
|
|||
from ai_prompter import Prompter
|
||||
from langchain_core.messages import AIMessage, SystemMessage
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
|
||||
from open_notebook.utils import clean_thinking_content
|
||||
from langgraph.checkpoint.sqlite import SqliteSaver
|
||||
from langgraph.graph import END, START, StateGraph
|
||||
from langgraph.graph.message import add_messages
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from open_notebook.ai.provision import provision_langchain_model
|
||||
from open_notebook.config import LANGGRAPH_CHECKPOINT_FILE
|
||||
from open_notebook.domain.notebook import Source, SourceInsight
|
||||
from open_notebook.graphs.utils import provision_langchain_model
|
||||
from open_notebook.utils import clean_thinking_content
|
||||
from open_notebook.utils.context_builder import ContextBuilder
|
||||
|
||||
|
||||
|
|
@ -111,7 +110,7 @@ def call_model_with_source_context(
|
|||
}
|
||||
|
||||
# Apply the source_chat prompt template
|
||||
system_prompt = Prompter(prompt_template="source_chat").render(data=prompt_data)
|
||||
system_prompt = Prompter(prompt_template="source_chat/system").render(data=prompt_data)
|
||||
payload = [SystemMessage(content=system_prompt)] + state.get("messages", [])
|
||||
|
||||
# Handle async model provisioning from sync context
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ from langchain_core.runnables import RunnableConfig
|
|||
from langgraph.graph import END, START, StateGraph
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from open_notebook.ai.provision import provision_langchain_model
|
||||
from open_notebook.domain.notebook import Source
|
||||
from open_notebook.domain.transformation import DefaultPrompts, Transformation
|
||||
from open_notebook.graphs.utils import provision_langchain_model
|
||||
from open_notebook.utils import clean_thinking_content
|
||||
|
||||
|
||||
|
|
|
|||
2
open_notebook/podcasts/__init__.py
Normal file
2
open_notebook/podcasts/__init__.py
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# Podcasts module
|
||||
# Contains podcast episode models, profiles, and generation logic
|
||||
|
|
@ -8,13 +8,13 @@ that can be tested without database mocking.
|
|||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from open_notebook.ai.models import ModelManager
|
||||
from open_notebook.domain.base import RecordModel
|
||||
from open_notebook.domain.content_settings import ContentSettings
|
||||
from open_notebook.domain.models import ModelManager
|
||||
from open_notebook.domain.notebook import Note, Notebook, Source
|
||||
from open_notebook.domain.podcast import EpisodeProfile, SpeakerProfile
|
||||
from open_notebook.domain.transformation import Transformation
|
||||
from open_notebook.exceptions import InvalidInputError
|
||||
from open_notebook.podcasts.models import EpisodeProfile, SpeakerProfile
|
||||
|
||||
# ============================================================================
|
||||
# TEST SUITE 1: RecordModel Singleton Pattern
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ class TestModelCreation:
|
|||
@patch("open_notebook.database.repository.repo_query")
|
||||
async def test_create_same_model_name_different_provider(self, mock_repo_query, client):
|
||||
"""Test that creating a model with same name but different provider is allowed."""
|
||||
from open_notebook.domain.models import Model
|
||||
from open_notebook.ai.models import Model
|
||||
|
||||
# Mock repo_query to return empty (no duplicate found for different provider)
|
||||
mock_repo_query.return_value = []
|
||||
|
|
|
|||
Loading…
Reference in a new issue