feat(image): apply generation settings live

This commit is contained in:
Xubin Ren
2026-07-23 12:42:24 +08:00
parent c7393c785e
commit 1616fa9f14
11 changed files with 481 additions and 18 deletions
+121
View File
@@ -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