feat: Enhance plugin runtime configuration and hook management
- Added `inactive_plugins` field to `RunnerReadyPayload` and `ReloadPluginResultPayload` to track plugins that are not activated due to being disabled or unmet dependencies. - Introduced `InspectPluginConfigPayload` and `InspectPluginConfigResultPayload` for inspecting plugin configuration metadata. - Implemented `PluginActivationStatus` enum to better represent plugin activation states. - Updated `_activate_plugin` method to return activation status and handle inactive plugins accordingly. - Added hooks for send service to allow modification of messages before and after sending. - Created new runtime routes for listing hook specifications in the WebUI. - Refactored plugin configuration handling to utilize runtime inspection for better accuracy and flexibility. - Enhanced error handling and logging for plugin configuration operations.
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from time import perf_counter
|
||||
from typing import List, Optional, Sequence
|
||||
from typing import Any, List, Optional, Sequence
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
@@ -26,6 +26,15 @@ from src.llm_models.model_client.base_client import BaseClient
|
||||
from src.llm_models.payload_content.message import Message, MessageBuilder, RoleType
|
||||
from src.llm_models.payload_content.resp_format import RespFormat, RespFormatType
|
||||
from src.llm_models.payload_content.tool_option import ToolCall, ToolDefinitionInput, ToolOption, normalize_tool_options
|
||||
from src.plugin_runtime.hook_payloads import (
|
||||
deserialize_prompt_messages,
|
||||
deserialize_tool_calls,
|
||||
serialize_prompt_messages,
|
||||
serialize_tool_calls,
|
||||
serialize_tool_definitions,
|
||||
)
|
||||
from src.plugin_runtime.hook_schema_utils import build_object_schema
|
||||
from src.plugin_runtime.host.hook_spec_registry import HookSpec, HookSpecRegistry
|
||||
from src.services.llm_service import LLMServiceClient
|
||||
|
||||
from .builtin_tools import get_builtin_tools
|
||||
@@ -58,6 +67,123 @@ class ToolFilterSelection(BaseModel):
|
||||
logger = get_logger("maisaka_chat_loop")
|
||||
|
||||
|
||||
def register_maisaka_hook_specs(registry: HookSpecRegistry) -> List[HookSpec]:
|
||||
"""注册 Maisaka 规划器内置 Hook 规格。
|
||||
|
||||
Args:
|
||||
registry: 目标 Hook 规格注册中心。
|
||||
|
||||
Returns:
|
||||
List[HookSpec]: 实际注册的 Hook 规格列表。
|
||||
"""
|
||||
|
||||
return registry.register_hook_specs(
|
||||
[
|
||||
HookSpec(
|
||||
name="maisaka.planner.before_request",
|
||||
description="在 Maisaka 向模型发起规划请求前触发,可改写消息窗口与工具定义。",
|
||||
parameters_schema=build_object_schema(
|
||||
{
|
||||
"messages": {
|
||||
"type": "array",
|
||||
"description": "即将发给模型的 PromptMessage 列表。",
|
||||
},
|
||||
"tool_definitions": {
|
||||
"type": "array",
|
||||
"description": "当前候选工具定义列表。",
|
||||
},
|
||||
"selected_history_count": {
|
||||
"type": "integer",
|
||||
"description": "当前选中的上下文消息数量。",
|
||||
},
|
||||
"built_message_count": {
|
||||
"type": "integer",
|
||||
"description": "实际发送给模型的消息数量。",
|
||||
},
|
||||
"selection_reason": {
|
||||
"type": "string",
|
||||
"description": "上下文选择说明。",
|
||||
},
|
||||
"session_id": {
|
||||
"type": "string",
|
||||
"description": "当前会话 ID。",
|
||||
},
|
||||
},
|
||||
required=[
|
||||
"messages",
|
||||
"tool_definitions",
|
||||
"selected_history_count",
|
||||
"built_message_count",
|
||||
"selection_reason",
|
||||
"session_id",
|
||||
],
|
||||
),
|
||||
default_timeout_ms=6000,
|
||||
allow_abort=False,
|
||||
allow_kwargs_mutation=True,
|
||||
),
|
||||
HookSpec(
|
||||
name="maisaka.planner.after_response",
|
||||
description="在 Maisaka 收到模型响应后触发,可调整文本结果与工具调用列表。",
|
||||
parameters_schema=build_object_schema(
|
||||
{
|
||||
"response": {
|
||||
"type": "string",
|
||||
"description": "模型返回的文本内容。",
|
||||
},
|
||||
"tool_calls": {
|
||||
"type": "array",
|
||||
"description": "模型返回的工具调用列表。",
|
||||
},
|
||||
"selected_history_count": {
|
||||
"type": "integer",
|
||||
"description": "当前选中的上下文消息数量。",
|
||||
},
|
||||
"built_message_count": {
|
||||
"type": "integer",
|
||||
"description": "实际发送给模型的消息数量。",
|
||||
},
|
||||
"selection_reason": {
|
||||
"type": "string",
|
||||
"description": "上下文选择说明。",
|
||||
},
|
||||
"session_id": {
|
||||
"type": "string",
|
||||
"description": "当前会话 ID。",
|
||||
},
|
||||
"prompt_tokens": {
|
||||
"type": "integer",
|
||||
"description": "输入 Token 数。",
|
||||
},
|
||||
"completion_tokens": {
|
||||
"type": "integer",
|
||||
"description": "输出 Token 数。",
|
||||
},
|
||||
"total_tokens": {
|
||||
"type": "integer",
|
||||
"description": "总 Token 数。",
|
||||
},
|
||||
},
|
||||
required=[
|
||||
"response",
|
||||
"tool_calls",
|
||||
"selected_history_count",
|
||||
"built_message_count",
|
||||
"selection_reason",
|
||||
"session_id",
|
||||
"prompt_tokens",
|
||||
"completion_tokens",
|
||||
"total_tokens",
|
||||
],
|
||||
),
|
||||
default_timeout_ms=6000,
|
||||
allow_abort=False,
|
||||
allow_kwargs_mutation=True,
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class MaisakaChatLoopService:
|
||||
"""负责 Maisaka 主对话循环、系统提示词和终端渲染。"""
|
||||
|
||||
@@ -105,6 +231,35 @@ class MaisakaChatLoopService:
|
||||
|
||||
return self._personality_prompt
|
||||
|
||||
@staticmethod
|
||||
def _get_runtime_manager() -> Any:
|
||||
"""获取插件运行时管理器。
|
||||
|
||||
Returns:
|
||||
Any: 插件运行时管理器单例。
|
||||
"""
|
||||
|
||||
from src.plugin_runtime.integration import get_plugin_runtime_manager
|
||||
|
||||
return get_plugin_runtime_manager()
|
||||
|
||||
@staticmethod
|
||||
def _coerce_int(value: Any, default: int) -> int:
|
||||
"""将任意值安全转换为整数。
|
||||
|
||||
Args:
|
||||
value: 待转换的输入值。
|
||||
default: 转换失败时的默认值。
|
||||
|
||||
Returns:
|
||||
int: 转换后的整数结果。
|
||||
"""
|
||||
|
||||
try:
|
||||
return int(value)
|
||||
except (TypeError, ValueError):
|
||||
return default
|
||||
|
||||
def _build_personality_prompt(self) -> str:
|
||||
"""构造人格提示词。"""
|
||||
|
||||
@@ -580,6 +735,26 @@ class MaisakaChatLoopService:
|
||||
else:
|
||||
all_tools = [*get_builtin_tools(), *self._extra_tools]
|
||||
|
||||
before_request_result = await self._get_runtime_manager().invoke_hook(
|
||||
"maisaka.planner.before_request",
|
||||
messages=serialize_prompt_messages(built_messages),
|
||||
tool_definitions=serialize_tool_definitions(all_tools),
|
||||
selected_history_count=len(selected_history),
|
||||
built_message_count=len(built_messages),
|
||||
selection_reason=selection_reason,
|
||||
session_id=self._session_id,
|
||||
)
|
||||
before_request_kwargs = before_request_result.kwargs
|
||||
raw_messages = before_request_kwargs.get("messages")
|
||||
if isinstance(raw_messages, list):
|
||||
try:
|
||||
built_messages = deserialize_prompt_messages(raw_messages)
|
||||
except Exception as exc:
|
||||
logger.warning(f"Hook maisaka.planner.before_request 返回的 messages 无法反序列化,已忽略: {exc}")
|
||||
raw_tool_definitions = before_request_kwargs.get("tool_definitions")
|
||||
if isinstance(raw_tool_definitions, list):
|
||||
all_tools = [item for item in raw_tool_definitions if isinstance(item, dict)]
|
||||
|
||||
ordered_panels = PromptCLIVisualizer.build_prompt_panels(
|
||||
built_messages,
|
||||
image_display_mode=global_config.maisaka.terminal_image_display_mode,
|
||||
@@ -625,33 +800,63 @@ class MaisakaChatLoopService:
|
||||
)
|
||||
logger.info(f"本轮Prompt统计: {prompt_stats_text}")
|
||||
|
||||
final_response = generation_result.response or ""
|
||||
final_tool_calls = list(generation_result.tool_calls or [])
|
||||
after_response_result = await self._get_runtime_manager().invoke_hook(
|
||||
"maisaka.planner.after_response",
|
||||
response=final_response,
|
||||
tool_calls=serialize_tool_calls(final_tool_calls),
|
||||
selected_history_count=len(selected_history),
|
||||
built_message_count=len(built_messages),
|
||||
selection_reason=selection_reason,
|
||||
session_id=self._session_id,
|
||||
prompt_tokens=generation_result.prompt_tokens,
|
||||
completion_tokens=generation_result.completion_tokens,
|
||||
total_tokens=generation_result.total_tokens,
|
||||
)
|
||||
after_response_kwargs = after_response_result.kwargs
|
||||
if "response" in after_response_kwargs:
|
||||
final_response = str(after_response_kwargs.get("response") or "")
|
||||
raw_tool_calls = after_response_kwargs.get("tool_calls")
|
||||
if isinstance(raw_tool_calls, list):
|
||||
try:
|
||||
final_tool_calls = deserialize_tool_calls(raw_tool_calls)
|
||||
except Exception as exc:
|
||||
logger.warning(f"Hook maisaka.planner.after_response 返回的 tool_calls 无法反序列化,已忽略: {exc}")
|
||||
prompt_tokens = self._coerce_int(after_response_kwargs.get("prompt_tokens"), generation_result.prompt_tokens)
|
||||
completion_tokens = self._coerce_int(
|
||||
after_response_kwargs.get("completion_tokens"),
|
||||
generation_result.completion_tokens,
|
||||
)
|
||||
total_tokens = self._coerce_int(after_response_kwargs.get("total_tokens"), generation_result.total_tokens)
|
||||
|
||||
tool_call_summaries = [
|
||||
{
|
||||
"调用编号": getattr(tool_call, "call_id", getattr(tool_call, "id", None)),
|
||||
"工具名": getattr(tool_call, "func_name", getattr(tool_call, "name", None)),
|
||||
"参数": getattr(tool_call, "args", getattr(tool_call, "arguments", None)),
|
||||
}
|
||||
for tool_call in (generation_result.tool_calls or [])
|
||||
for tool_call in final_tool_calls
|
||||
]
|
||||
logger.info(
|
||||
f"Maisaka 规划器返回结果: 内容={generation_result.response or ''!r} "
|
||||
f"Maisaka 规划器返回结果: 内容={final_response!r} "
|
||||
f"工具调用={tool_call_summaries}"
|
||||
)
|
||||
|
||||
raw_message = AssistantMessage(
|
||||
content=generation_result.response or "",
|
||||
content=final_response,
|
||||
timestamp=datetime.now(),
|
||||
tool_calls=generation_result.tool_calls or [],
|
||||
tool_calls=final_tool_calls,
|
||||
)
|
||||
return ChatResponse(
|
||||
content=generation_result.response,
|
||||
tool_calls=generation_result.tool_calls or [],
|
||||
content=final_response or None,
|
||||
tool_calls=final_tool_calls,
|
||||
raw_message=raw_message,
|
||||
selected_history_count=len(selected_history),
|
||||
prompt_tokens=generation_result.prompt_tokens,
|
||||
prompt_tokens=prompt_tokens,
|
||||
built_message_count=len(built_messages),
|
||||
completion_tokens=generation_result.completion_tokens,
|
||||
total_tokens=generation_result.total_tokens,
|
||||
completion_tokens=completion_tokens,
|
||||
total_tokens=total_tokens,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
||||
Reference in New Issue
Block a user