init
This commit is contained in:
173
reelforge/core/config_manager.py
Normal file
173
reelforge/core/config_manager.py
Normal file
@@ -0,0 +1,173 @@
|
||||
"""
|
||||
Configuration Manager
|
||||
|
||||
Manages capability configuration and provides unified access to MCP tools.
|
||||
Simplified from Router - only handles config injection, no external routing.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from fastmcp import Client
|
||||
from loguru import logger
|
||||
|
||||
from reelforge.core.conventions import CapabilityInfo
|
||||
|
||||
|
||||
class ConfigManager:
|
||||
"""
|
||||
Configuration manager for capabilities
|
||||
|
||||
Core responsibilities:
|
||||
1. Manage active capability selection
|
||||
2. Inject configuration into capability calls
|
||||
3. Provide unified MCP client interface
|
||||
|
||||
No external MCP routing - all capabilities are builtin.
|
||||
"""
|
||||
|
||||
def __init__(self, registry, config: dict):
|
||||
"""
|
||||
Initialize config manager
|
||||
|
||||
Args:
|
||||
registry: CapabilityRegistry instance with registered capabilities
|
||||
config: Application configuration dict
|
||||
"""
|
||||
self.registry = registry
|
||||
self.config = config
|
||||
self._active: dict[str, str] = {} # type -> id
|
||||
|
||||
# Create in-memory MCP client for calling builtin capabilities
|
||||
self._local_client = Client(registry.local_mcp)
|
||||
|
||||
self._load_active_from_config()
|
||||
|
||||
def _load_active_from_config(self):
|
||||
"""Load active capability selections from config"""
|
||||
for cap_type in self.registry.capabilities.keys():
|
||||
# Read active from flat structure: config[type]["default"]
|
||||
cap_section = self.config.get(cap_type, {})
|
||||
if isinstance(cap_section, dict):
|
||||
configured_id = cap_section.get("default")
|
||||
if configured_id and configured_id in self.registry.capabilities[cap_type]:
|
||||
self._active[cap_type] = configured_id
|
||||
continue
|
||||
|
||||
# Otherwise, auto-select default
|
||||
self._auto_select_default(cap_type)
|
||||
|
||||
def _auto_select_default(self, cap_type: str):
|
||||
"""Auto-select default capability"""
|
||||
capabilities = self.registry.capabilities[cap_type]
|
||||
|
||||
# Find default
|
||||
for cap_id, cap_info in capabilities.items():
|
||||
if cap_info.is_default:
|
||||
self._active[cap_type] = cap_id
|
||||
logger.info(f"✓ Auto-selected default {cap_type}: {cap_id}")
|
||||
return
|
||||
|
||||
# No default, use first
|
||||
if capabilities:
|
||||
first_id = next(iter(capabilities.keys()))
|
||||
self._active[cap_type] = first_id
|
||||
logger.info(f"✓ Auto-selected first {cap_type}: {first_id}")
|
||||
|
||||
def get_active(self, cap_type: str) -> str | None:
|
||||
"""Get active capability ID for a type"""
|
||||
return self._active.get(cap_type)
|
||||
|
||||
def get_available_ids(self, cap_type: str) -> list[str]:
|
||||
"""Get available capability IDs for a type"""
|
||||
return list(self.registry.capabilities.get(cap_type, {}).keys())
|
||||
|
||||
async def call(self, cap_type: str, cap_id: str | None = None, **kwargs) -> Any:
|
||||
"""
|
||||
Call a capability with config injection
|
||||
|
||||
Args:
|
||||
cap_type: Capability type (e.g., "llm", "tts")
|
||||
cap_id: Specific capability ID (defaults to active)
|
||||
**kwargs: Arguments for the capability
|
||||
|
||||
Returns:
|
||||
Result from the capability
|
||||
"""
|
||||
# Determine which capability to use
|
||||
if cap_id is None:
|
||||
cap_id = self.get_active(cap_type)
|
||||
|
||||
if not cap_id:
|
||||
raise ValueError(f"No active capability for type: {cap_type}")
|
||||
|
||||
# Get capability info
|
||||
cap_info = self.registry.capabilities[cap_type][cap_id]
|
||||
|
||||
logger.debug(f"Calling {cap_info.full_id} ({cap_info.display_label})")
|
||||
|
||||
# Prepare tool arguments with config injection
|
||||
tool_arguments = self._inject_config(cap_info, kwargs)
|
||||
|
||||
# Call tool via MCP protocol using in-memory client
|
||||
async with self._local_client:
|
||||
result = await self._local_client.call_tool(
|
||||
name=cap_info.tool_name,
|
||||
arguments=tool_arguments
|
||||
)
|
||||
|
||||
# Extract content from MCP result
|
||||
return self._extract_content(result)
|
||||
|
||||
def _inject_config(self, cap_info: CapabilityInfo, kwargs: dict) -> dict:
|
||||
"""
|
||||
Inject configuration into tool arguments
|
||||
|
||||
This is the core value of ConfigManager - it handles config injection
|
||||
so individual capabilities don't need to know about config.yaml
|
||||
"""
|
||||
tool_arguments = kwargs.copy()
|
||||
|
||||
# Handle LLM-specific configuration (direct top-level config)
|
||||
if cap_info.type == "llm":
|
||||
llm_config = self.config.get("llm", {})
|
||||
|
||||
# Add LLM credentials and settings (if not already provided)
|
||||
tool_arguments.setdefault("api_key", llm_config.get("api_key", ""))
|
||||
tool_arguments.setdefault("base_url", llm_config.get("base_url", ""))
|
||||
tool_arguments.setdefault("model", llm_config.get("model", ""))
|
||||
|
||||
logger.debug(f"LLM using: {tool_arguments['model']} @ {tool_arguments['base_url']}")
|
||||
|
||||
# Handle other capability types (image, tts, etc.)
|
||||
else:
|
||||
# Read capability-specific config from flat structure: config[type][id]
|
||||
cap_section = self.config.get(cap_info.type, {})
|
||||
if isinstance(cap_section, dict):
|
||||
cap_config = cap_section.get(cap_info.id, {})
|
||||
if isinstance(cap_config, dict):
|
||||
# Merge config (don't override kwargs)
|
||||
for key, value in cap_config.items():
|
||||
tool_arguments.setdefault(key, value)
|
||||
|
||||
return tool_arguments
|
||||
|
||||
def _extract_content(self, result: Any) -> Any:
|
||||
"""
|
||||
Extract content from MCP result
|
||||
|
||||
MCP returns a CallToolResult with content field
|
||||
"""
|
||||
if hasattr(result, 'content'):
|
||||
# Handle different content types
|
||||
if isinstance(result.content, list):
|
||||
# Multiple content items, join them
|
||||
return '\n'.join(
|
||||
item.text if hasattr(item, 'text') else str(item)
|
||||
for item in result.content
|
||||
)
|
||||
else:
|
||||
return result.content
|
||||
|
||||
# Fallback: return the result as-is
|
||||
return result
|
||||
|
||||
Reference in New Issue
Block a user