Ctrl K
ComunidadGuías…

butler/orchestrator/__init__.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""Butler Orchestrator — bridges Butler product layer with the Agent Loop."""

from __future__ import annotations

import logging
import threading
from typing import TYPE_CHECKING, Any, cast

if TYPE_CHECKING:
    from butler.core.agent_loop.loop import AgentLoop
    from butler.transport.llm_client import LLMClient

from butler.configuration.settings import get_butler_settings
from butler.memory import ButlerMemory, ProjectMemory
from butler.memory.facade import ButlerMemoryService  # test patch target
from butler.orchestrator import loop_factory, memory_bridge, prompt_assembler, skill_bridge
from butler.orchestrator.templates import normalize_butler_role
from butler.project.manager import get_project_manager
from butler.skills.router import SkillRouter

import butler.workflows  # noqa: F401 — register workflow hooks

logger = logging.getLogger(__name__)


class ButlerOrchestrator:
    """Bridge Butler configuration/memories into AgentLoop instances."""

    def __init__(self, user_id: str = "owner", channel: str = "cli") -> None:
        self.user_id = user_id
        self.channel = channel
        self._settings = get_butler_settings()
        self._memory_by_tenant: dict[str, ButlerMemory] = {}
        self._memory_lock = threading.Lock()
        self.project_manager = get_project_manager()
        self._project_memory: ProjectMemory | None = None
        self._skill_router: SkillRouter | None = None
        self._skill_manager = None
        self.memory_provider = None
        self.memory_offline: bool = False
        self._memory_init_error: str = ""
        memory_bridge.reload_project_memory(self)
        skill_bridge.rebuild_skill_router(self)
        memory_bridge.initialize_memory_provider(self)
        self.project_manager.on_switch(self._pm_switch)

    def _pm_switch(self, old_project: str, new_project: str) -> None:
        self.on_project_switch(old_project, new_project)

    @property
    def butler_memory(self) -> ButlerMemory:
        return memory_bridge.get_butler_memory(self)

    def _model_credentials(self, role: str) -> dict[str, Any]:
        from butler.model_resolve import model_config_to_credentials, resolve_effective_model

        role = normalize_butler_role(role)
        project = self.project_manager.get_current()
        em = resolve_effective_model(role, project=project, settings=self._settings)
        return cast(dict[str, Any], model_config_to_credentials(em.config, settings=self._settings))

    def build_memory_context(self, *, for_role: str = "default") -> str:
        return cast(str, memory_bridge.build_memory_context(self, for_role=for_role))

    def _system_prompt_placeholders(self, *, for_role: str = "default") -> dict[str, str]:
        return cast(
            dict[str, str],
            prompt_assembler.system_prompt_placeholders(self, for_role=for_role),
        )

    def build_dynamic_system_reminder(self, *, for_role: str = "default") -> str:
        return cast(
            str,
            prompt_assembler.build_dynamic_system_reminder(self, for_role=for_role),
        )

    def build_static_system_prompt(self) -> str:
        return cast(str, prompt_assembler.build_static_system_prompt(self))

    def resolve_system_prompt(
        self,
        *,
        role: str = "butler",
        session_key: str = "",
    ) -> tuple[str, str | None]:
        return cast(
            tuple[str, str | None],
            prompt_assembler.resolve_system_prompt(
                self, role=role, session_key=session_key
            ),
        )

    def _assemble_default_system_prompt(self, *, for_role: str = "default") -> str:
        return cast(
            str,
            prompt_assembler.assemble_default_system_prompt(self, for_role=for_role),
        )

    def build_system_prompt(self) -> str:
        return cast(str, prompt_assembler.build_system_prompt(self))

    def build_lead_system_prompt(self, *, session_key: str = "") -> str:
        return cast(
            str,
            prompt_assembler.build_lead_system_prompt(self, session_key=session_key),
        )

    def get_agent_kwargs(self) -> dict[str, Any]:
        return cast(dict[str, Any], loop_factory.get_agent_kwargs(self))

    def create_llm_client(self, role: str = "butler") -> LLMClient:
        return loop_factory.create_llm_client(self, role)

    def create_agent_loop(
        self,
        role: str = "butler",
        *,
        tools: list[dict[str, Any]] | None = None,
        tool_dispatcher: Any = None,
        callbacks: Any = None,
        session_key: str = "",
    ) -> AgentLoop:
        return loop_factory.create_agent_loop(
            self,
            role,
            tools=tools,
            tool_dispatcher=tool_dispatcher,
            callbacks=callbacks,
            session_key=session_key,
        )

    def create_project_agent_loop(
        self,
        role: str = "dev",
        *,
        tools: list[dict[str, Any]] | None = None,
        tool_dispatcher: Any = None,
        callbacks: Any = None,
        session_key: str = "",
    ) -> AgentLoop:
        return loop_factory.create_project_agent_loop(
            self,
            role,
            tools=tools,
            tool_dispatcher=tool_dispatcher,
            callbacks=callbacks,
            session_key=session_key,
        )

    def get_project_agent_kwargs(self, role: str) -> dict[str, Any]:
        return cast(dict[str, Any], loop_factory.get_project_agent_kwargs(self, role))

    def on_project_switch(self, old_project: str, new_project: str) -> None:
        logger.debug(
            "Butler project switched: %r -> %r",
            old_project,
            new_project,
        )
        memory_bridge.reload_project_memory(self)
        skill_bridge.rebuild_skill_router(self)
        memory_bridge.refresh_memory_provider_for_project_switch(self)

    def _reload_project_memory(self) -> None:
        memory_bridge.reload_project_memory(self)

    def _rebuild_skill_router(self) -> None:
        skill_bridge.rebuild_skill_router(self)

    def _initialize_memory_provider(self) -> None:
        memory_bridge.initialize_memory_provider(self)

    def _refresh_memory_provider_for_project_switch(self) -> None:
        memory_bridge.refresh_memory_provider_for_project_switch(self)

    def _build_skill_injection_sections(
        self,
        matched: list[dict[str, Any]],
        *,
        header_note: str,
    ) -> list[str]:
        return cast(
            list[str],
            skill_bridge.build_skill_injection_sections(
                matched, header_note=header_note
            ),
        )

    def inject_skill_context(
        self,
        task_description: str,
        top_k: int = 3,
        *,
        diagnostics: dict[str, Any] | None = None,
    ) -> str:
        return cast(
            str,
            skill_bridge.inject_skill_context(
                self,
                task_description,
                top_k=top_k,
                diagnostics=diagnostics,
            ),
        )


__all__ = [
    "ButlerOrchestrator",
    "ButlerMemoryService",
    "build_dynamic_system_reminder",
    "build_lead_system_prompt",
    "build_memory_context",
    "build_static_system_prompt",
    "build_system_prompt",
    "butler_memory",
    "create_agent_loop",
    "create_llm_client",
    "create_project_agent_loop",
    "get_agent_kwargs",
    "get_project_agent_kwargs",
    "inject_skill_context",
    "on_project_switch",
    "resolve_system_prompt",
]