feat(image): apply generation settings live
This commit is contained in:
@@ -2,24 +2,34 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from loguru import logger
|
||||
from pydantic import Field
|
||||
|
||||
from nanobot.agent.tools.base import Tool, ToolResult, tool_parameters
|
||||
from nanobot.agent.tools.registry import ToolRegistry
|
||||
from nanobot.agent.tools.schema import (
|
||||
ArraySchema,
|
||||
IntegerSchema,
|
||||
StringSchema,
|
||||
tool_parameters_schema,
|
||||
)
|
||||
from nanobot.bus.events import (
|
||||
INBOUND_META_RUNTIME_CONTROL,
|
||||
RUNTIME_CONTROL_ACK,
|
||||
RUNTIME_CONTROL_IMAGE_GENERATION_RELOAD,
|
||||
InboundMessage,
|
||||
)
|
||||
from nanobot.config.paths import get_media_dir
|
||||
from nanobot.config_base import Base
|
||||
from nanobot.providers.image_generation import (
|
||||
ImageGenerationError,
|
||||
ImageGenerationProvider,
|
||||
get_image_gen_provider,
|
||||
image_gen_provider_configs,
|
||||
)
|
||||
from nanobot.security.workspace_access import current_tool_workspace
|
||||
from nanobot.security.workspace_policy import WorkspaceBoundaryError, resolve_allowed_path
|
||||
@@ -208,3 +218,114 @@ class ImageGenerationTool(Tool):
|
||||
return generated_image_tool_result(artifacts)
|
||||
except (ArtifactError, ImageGenerationError, OSError) as exc:
|
||||
return ToolResult.error(f"Error: {exc}")
|
||||
|
||||
|
||||
async def reload_image_generation_tool(state: Any, registry: ToolRegistry) -> dict[str, Any]:
|
||||
"""Apply the persisted image configuration to the running agent."""
|
||||
try:
|
||||
from nanobot.config.loader import load_config, resolve_config_env_vars
|
||||
|
||||
config = resolve_config_env_vars(load_config())
|
||||
tool_config = config.tools.image_generation
|
||||
provider_configs = image_gen_provider_configs(config)
|
||||
except Exception as exc:
|
||||
logger.warning("Image generation hot reload could not read config: {}", exc)
|
||||
return {
|
||||
"ok": False,
|
||||
"message": "Could not reload image generation config.",
|
||||
"requires_restart": True,
|
||||
"error": str(exc),
|
||||
}
|
||||
|
||||
next_tool = (
|
||||
ImageGenerationTool(
|
||||
workspace=state.workspace,
|
||||
config=tool_config,
|
||||
provider_configs=provider_configs,
|
||||
)
|
||||
if tool_config.enabled
|
||||
else None
|
||||
)
|
||||
|
||||
state.tools_config.image_generation = tool_config
|
||||
state._image_generation_provider_configs = provider_configs
|
||||
if next_tool is not None:
|
||||
registry.register(next_tool)
|
||||
else:
|
||||
registry.unregister("generate_image")
|
||||
|
||||
logger.info(
|
||||
"Image generation config reloaded: enabled={} provider={} model={}",
|
||||
tool_config.enabled,
|
||||
tool_config.provider,
|
||||
tool_config.model,
|
||||
)
|
||||
return {
|
||||
"ok": True,
|
||||
"message": "Image generation settings applied without restarting nanobot.",
|
||||
"enabled": tool_config.enabled,
|
||||
"provider": tool_config.provider,
|
||||
"model": tool_config.model,
|
||||
"requires_restart": False,
|
||||
}
|
||||
|
||||
|
||||
async def request_image_generation_reload(
|
||||
bus: Any,
|
||||
*,
|
||||
timeout: float = 5.0,
|
||||
) -> dict[str, Any]:
|
||||
"""Ask the running agent loop to refresh its image generation tool."""
|
||||
loop = asyncio.get_running_loop()
|
||||
ack: asyncio.Future[dict[str, Any]] = loop.create_future()
|
||||
await bus.publish_inbound(
|
||||
InboundMessage(
|
||||
channel="system",
|
||||
sender_id="webui-settings",
|
||||
chat_id="runtime",
|
||||
content=RUNTIME_CONTROL_IMAGE_GENERATION_RELOAD,
|
||||
metadata={
|
||||
INBOUND_META_RUNTIME_CONTROL: RUNTIME_CONTROL_IMAGE_GENERATION_RELOAD,
|
||||
RUNTIME_CONTROL_ACK: ack,
|
||||
},
|
||||
)
|
||||
)
|
||||
try:
|
||||
result = await asyncio.wait_for(ack, timeout=timeout)
|
||||
except asyncio.TimeoutError:
|
||||
return {
|
||||
"ok": False,
|
||||
"message": "Image generation hot reload timed out.",
|
||||
"requires_restart": True,
|
||||
}
|
||||
return result if isinstance(result, dict) else {
|
||||
"ok": False,
|
||||
"message": "Image generation hot reload returned an unexpected response.",
|
||||
"requires_restart": True,
|
||||
}
|
||||
|
||||
|
||||
async def handle_runtime_control(
|
||||
state: Any,
|
||||
msg: InboundMessage,
|
||||
registry: ToolRegistry,
|
||||
) -> bool:
|
||||
"""Handle an in-process image generation reload request."""
|
||||
metadata = msg.metadata if isinstance(msg.metadata, dict) else {}
|
||||
if metadata.get(INBOUND_META_RUNTIME_CONTROL) != RUNTIME_CONTROL_IMAGE_GENERATION_RELOAD:
|
||||
return False
|
||||
|
||||
ack = metadata.get(RUNTIME_CONTROL_ACK)
|
||||
try:
|
||||
result = await reload_image_generation_tool(state, registry)
|
||||
except Exception as exc:
|
||||
logger.exception("Image generation hot reload failed")
|
||||
result = {
|
||||
"ok": False,
|
||||
"message": "Image generation hot reload failed.",
|
||||
"requires_restart": True,
|
||||
"error": str(exc),
|
||||
}
|
||||
if isinstance(ack, asyncio.Future) and not ack.done():
|
||||
ack.set_result(result)
|
||||
return True
|
||||
|
||||
Reference in New Issue
Block a user