feat: 更新配置版本至 8.4.0,新增工具筛选相关字段和方法
This commit is contained in:
@@ -57,7 +57,7 @@ CONFIG_DIR: Path = PROJECT_ROOT / "config"
|
|||||||
BOT_CONFIG_PATH: Path = (CONFIG_DIR / "bot_config.toml").resolve().absolute()
|
BOT_CONFIG_PATH: Path = (CONFIG_DIR / "bot_config.toml").resolve().absolute()
|
||||||
MODEL_CONFIG_PATH: Path = (CONFIG_DIR / "model_config.toml").resolve().absolute()
|
MODEL_CONFIG_PATH: Path = (CONFIG_DIR / "model_config.toml").resolve().absolute()
|
||||||
MMC_VERSION: str = "1.0.0"
|
MMC_VERSION: str = "1.0.0"
|
||||||
CONFIG_VERSION: str = "8.3.0"
|
CONFIG_VERSION: str = "8.2.0"
|
||||||
MODEL_CONFIG_VERSION: str = "1.13.1"
|
MODEL_CONFIG_VERSION: str = "1.13.1"
|
||||||
|
|
||||||
logger = get_logger("config")
|
logger = get_logger("config")
|
||||||
|
|||||||
@@ -1550,6 +1550,35 @@ class MaiSakaConfig(ConfigBase):
|
|||||||
)
|
)
|
||||||
"""每个入站消息的最大内部规划轮数"""
|
"""每个入站消息的最大内部规划轮数"""
|
||||||
|
|
||||||
|
tool_filter_task_name: str = Field(
|
||||||
|
default="utils",
|
||||||
|
json_schema_extra={
|
||||||
|
"x-widget": "input",
|
||||||
|
"x-icon": "sparkles",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
"""工具筛选预判使用的模型任务名"""
|
||||||
|
|
||||||
|
tool_filter_threshold: int = Field(
|
||||||
|
default=20,
|
||||||
|
ge=1,
|
||||||
|
json_schema_extra={
|
||||||
|
"x-widget": "input",
|
||||||
|
"x-icon": "filter",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
"""当可用工具总数超过该阈值时,先进行一轮工具筛选"""
|
||||||
|
|
||||||
|
tool_filter_max_keep: int = Field(
|
||||||
|
default=5,
|
||||||
|
ge=1,
|
||||||
|
json_schema_extra={
|
||||||
|
"x-widget": "input",
|
||||||
|
"x-icon": "list-filter",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
"""工具筛选阶段最多保留的非内置工具数量"""
|
||||||
|
|
||||||
terminal_image_preview: bool = Field(
|
terminal_image_preview: bool = Field(
|
||||||
default=False,
|
default=False,
|
||||||
json_schema_extra={
|
json_schema_extra={
|
||||||
|
|||||||
@@ -8,9 +8,11 @@ from time import perf_counter
|
|||||||
from typing import Any, Dict, List, Optional, Sequence
|
from typing import Any, Dict, List, Optional, Sequence
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import json
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from PIL import Image as PILImage
|
from PIL import Image as PILImage
|
||||||
|
from pydantic import BaseModel, Field as PydanticField
|
||||||
from rich.console import Group, RenderableType
|
from rich.console import Group, RenderableType
|
||||||
from rich.panel import Panel
|
from rich.panel import Panel
|
||||||
from rich.pretty import Pretty
|
from rich.pretty import Pretty
|
||||||
@@ -22,10 +24,11 @@ from src.common.data_models.message_component_data_model import MessageSequence,
|
|||||||
from src.common.logger import get_logger
|
from src.common.logger import get_logger
|
||||||
from src.common.prompt_i18n import load_prompt
|
from src.common.prompt_i18n import load_prompt
|
||||||
from src.config.config import global_config
|
from src.config.config import global_config
|
||||||
from src.core.tooling import ToolRegistry
|
from src.core.tooling import ToolRegistry, ToolSpec
|
||||||
from src.know_u.knowledge import extract_category_ids_from_result
|
from src.know_u.knowledge import extract_category_ids_from_result
|
||||||
from src.llm_models.model_client.base_client import BaseClient
|
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.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.llm_models.payload_content.tool_option import ToolCall, ToolDefinitionInput, ToolOption, normalize_tool_options
|
||||||
from src.services.llm_service import LLMServiceClient
|
from src.services.llm_service import LLMServiceClient
|
||||||
|
|
||||||
@@ -43,6 +46,13 @@ class ChatResponse:
|
|||||||
raw_message: AssistantMessage
|
raw_message: AssistantMessage
|
||||||
|
|
||||||
|
|
||||||
|
class ToolFilterSelection(BaseModel):
|
||||||
|
"""工具筛选响应。"""
|
||||||
|
|
||||||
|
selected_tool_names: list[str] = PydanticField(default_factory=list)
|
||||||
|
"""经过预筛后保留的候选工具名称列表。"""
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger("maisaka_chat_loop")
|
logger = get_logger("maisaka_chat_loop")
|
||||||
|
|
||||||
|
|
||||||
@@ -76,6 +86,10 @@ class MaisakaChatLoopService:
|
|||||||
else:
|
else:
|
||||||
self._chat_system_prompt = chat_system_prompt
|
self._chat_system_prompt = chat_system_prompt
|
||||||
self._llm_chat = LLMServiceClient(task_name="planner", request_type="maisaka_planner")
|
self._llm_chat = LLMServiceClient(task_name="planner", request_type="maisaka_planner")
|
||||||
|
self._tool_filter_llm = LLMServiceClient(
|
||||||
|
task_name=global_config.maisaka.tool_filter_task_name,
|
||||||
|
request_type="maisaka_tool_filter",
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def personality_prompt(self) -> str:
|
def personality_prompt(self) -> str:
|
||||||
@@ -156,7 +170,15 @@ class MaisakaChatLoopService:
|
|||||||
self._interrupt_flag = interrupt_flag
|
self._interrupt_flag = interrupt_flag
|
||||||
|
|
||||||
def _build_request_messages(self, selected_history: List[LLMContextMessage]) -> List[Message]:
|
def _build_request_messages(self, selected_history: List[LLMContextMessage]) -> List[Message]:
|
||||||
"""构造发给大模型的消息列表。"""
|
"""构造发给大模型的消息列表。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
selected_history: 已选中的上下文消息列表。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Message]: 发送给大模型的消息列表。
|
||||||
|
"""
|
||||||
|
|
||||||
messages: List[Message] = []
|
messages: List[Message] = []
|
||||||
system_msg = MessageBuilder().set_role(RoleType.System)
|
system_msg = MessageBuilder().set_role(RoleType.System)
|
||||||
system_msg.add_text_content(self._chat_system_prompt)
|
system_msg.add_text_content(self._chat_system_prompt)
|
||||||
@@ -169,6 +191,248 @@ class MaisakaChatLoopService:
|
|||||||
|
|
||||||
return messages
|
return messages
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _is_builtin_tool_spec(tool_spec: ToolSpec) -> bool:
|
||||||
|
"""判断一个工具是否属于默认内置工具。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tool_spec: 待判断的工具声明。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 是否为默认内置工具。
|
||||||
|
"""
|
||||||
|
|
||||||
|
return tool_spec.provider_type == "builtin" or tool_spec.provider_name == "maisaka_builtin"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _split_builtin_and_candidate_tools(
|
||||||
|
cls,
|
||||||
|
tool_specs: List[ToolSpec],
|
||||||
|
) -> tuple[List[ToolSpec], List[ToolSpec]]:
|
||||||
|
"""拆分内置工具与可筛选工具列表。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tool_specs: 当前全部工具声明。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple[List[ToolSpec], List[ToolSpec]]: `(内置工具, 可筛选工具)`。
|
||||||
|
"""
|
||||||
|
|
||||||
|
builtin_tool_specs: List[ToolSpec] = []
|
||||||
|
candidate_tool_specs: List[ToolSpec] = []
|
||||||
|
for tool_spec in tool_specs:
|
||||||
|
if cls._is_builtin_tool_spec(tool_spec):
|
||||||
|
builtin_tool_specs.append(tool_spec)
|
||||||
|
else:
|
||||||
|
candidate_tool_specs.append(tool_spec)
|
||||||
|
return builtin_tool_specs, candidate_tool_specs
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _truncate_tool_filter_text(text: str, max_length: int = 180) -> str:
|
||||||
|
"""截断工具筛选阶段展示的文本。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text: 原始文本。
|
||||||
|
max_length: 最长保留字符数。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 截断后的文本。
|
||||||
|
"""
|
||||||
|
|
||||||
|
normalized_text = text.strip()
|
||||||
|
if len(normalized_text) <= max_length:
|
||||||
|
return normalized_text
|
||||||
|
return f"{normalized_text[: max_length - 1]}…"
|
||||||
|
|
||||||
|
def _build_tool_filter_prompt(
|
||||||
|
self,
|
||||||
|
selected_history: List[LLMContextMessage],
|
||||||
|
candidate_tool_specs: List[ToolSpec],
|
||||||
|
max_keep: int,
|
||||||
|
) -> str:
|
||||||
|
"""构造小模型工具预筛选提示词。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
selected_history: 已选中的对话上下文。
|
||||||
|
candidate_tool_specs: 非内置候选工具列表。
|
||||||
|
max_keep: 最多保留的候选工具数量。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 用于工具预筛的小模型提示词。
|
||||||
|
"""
|
||||||
|
|
||||||
|
history_lines: List[str] = []
|
||||||
|
for message in selected_history[-10:]:
|
||||||
|
plain_text = message.processed_plain_text.strip()
|
||||||
|
if not plain_text:
|
||||||
|
continue
|
||||||
|
history_lines.append(
|
||||||
|
f"- {message.role}: {self._truncate_tool_filter_text(plain_text, max_length=200)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if history_lines:
|
||||||
|
history_section = "\n".join(history_lines)
|
||||||
|
else:
|
||||||
|
history_section = "- 当前没有可用的对话上下文。"
|
||||||
|
|
||||||
|
tool_lines = [
|
||||||
|
f"- {tool_spec.name}: {tool_spec.brief_description.strip() or '无简要描述'}"
|
||||||
|
for tool_spec in candidate_tool_specs
|
||||||
|
]
|
||||||
|
tool_section = "\n".join(tool_lines) if tool_lines else "- 当前没有候选工具。"
|
||||||
|
|
||||||
|
return (
|
||||||
|
"你是 Maisaka 的工具预筛选器。\n"
|
||||||
|
"你的任务是在正式进入 planner 前,根据当前情景从候选工具中挑出最可能马上会用到的工具。\n"
|
||||||
|
"默认内置工具已经自动保留,不在候选列表中,你不需要再次选择它们。\n"
|
||||||
|
"你只能参考工具的简要描述,不要假设未描述的隐藏能力。\n"
|
||||||
|
f"最多保留 {max_keep} 个候选工具;如果都不合适,可以返回空数组。\n"
|
||||||
|
"请严格返回 JSON 对象,格式为:"
|
||||||
|
'{"selected_tool_names":["工具名1","工具名2"]}\n\n'
|
||||||
|
f"【最近对话】\n{history_section}\n\n"
|
||||||
|
f"【候选工具(仅简要描述)】\n{tool_section}"
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _parse_tool_filter_response(
|
||||||
|
response_text: str,
|
||||||
|
candidate_tool_specs: List[ToolSpec],
|
||||||
|
max_keep: int,
|
||||||
|
) -> List[ToolSpec] | None:
|
||||||
|
"""解析工具预筛选响应。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
response_text: 小模型返回的原始文本。
|
||||||
|
candidate_tool_specs: 非内置候选工具列表。
|
||||||
|
max_keep: 最多保留的候选工具数量。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[ToolSpec] | None: 成功解析时返回筛选后的工具列表;解析失败时返回 ``None``。
|
||||||
|
"""
|
||||||
|
|
||||||
|
normalized_response = response_text.strip()
|
||||||
|
if not normalized_response:
|
||||||
|
return None
|
||||||
|
|
||||||
|
selected_tool_names: List[str]
|
||||||
|
try:
|
||||||
|
selected_tool_names = ToolFilterSelection.model_validate_json(normalized_response).selected_tool_names
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
parsed_payload = json.loads(normalized_response)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if isinstance(parsed_payload, dict):
|
||||||
|
raw_tool_names = parsed_payload.get("selected_tool_names", [])
|
||||||
|
elif isinstance(parsed_payload, list):
|
||||||
|
raw_tool_names = parsed_payload
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not isinstance(raw_tool_names, list):
|
||||||
|
return None
|
||||||
|
|
||||||
|
selected_tool_names = []
|
||||||
|
for item in raw_tool_names:
|
||||||
|
normalized_name = str(item).strip()
|
||||||
|
if normalized_name:
|
||||||
|
selected_tool_names.append(normalized_name)
|
||||||
|
|
||||||
|
candidate_map = {tool_spec.name: tool_spec for tool_spec in candidate_tool_specs}
|
||||||
|
filtered_tool_specs: List[ToolSpec] = []
|
||||||
|
seen_names: set[str] = set()
|
||||||
|
for tool_name in selected_tool_names:
|
||||||
|
normalized_name = tool_name.strip()
|
||||||
|
if not normalized_name or normalized_name in seen_names:
|
||||||
|
continue
|
||||||
|
tool_spec = candidate_map.get(normalized_name)
|
||||||
|
if tool_spec is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
seen_names.add(normalized_name)
|
||||||
|
filtered_tool_specs.append(tool_spec)
|
||||||
|
if len(filtered_tool_specs) >= max_keep:
|
||||||
|
break
|
||||||
|
|
||||||
|
return filtered_tool_specs
|
||||||
|
|
||||||
|
async def _filter_tool_specs_for_planner(
|
||||||
|
self,
|
||||||
|
selected_history: List[LLMContextMessage],
|
||||||
|
tool_specs: List[ToolSpec],
|
||||||
|
) -> List[ToolSpec]:
|
||||||
|
"""在将工具交给 planner 前进行快速预筛选。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
selected_history: 已选中的对话上下文。
|
||||||
|
tool_specs: 当前全部可用工具声明。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[ToolSpec]: 最终交给 planner 的工具声明列表。
|
||||||
|
"""
|
||||||
|
|
||||||
|
threshold = max(1, int(global_config.maisaka.tool_filter_threshold))
|
||||||
|
max_keep = max(1, int(global_config.maisaka.tool_filter_max_keep))
|
||||||
|
if len(tool_specs) <= threshold:
|
||||||
|
return tool_specs
|
||||||
|
|
||||||
|
builtin_tool_specs, candidate_tool_specs = self._split_builtin_and_candidate_tools(tool_specs)
|
||||||
|
if not candidate_tool_specs:
|
||||||
|
return tool_specs
|
||||||
|
if len(candidate_tool_specs) <= max_keep:
|
||||||
|
return [*builtin_tool_specs, *candidate_tool_specs]
|
||||||
|
|
||||||
|
filter_prompt = self._build_tool_filter_prompt(selected_history, candidate_tool_specs, max_keep)
|
||||||
|
logger.info(
|
||||||
|
"工具预筛选开始: "
|
||||||
|
f"总工具数={len(tool_specs)} "
|
||||||
|
f"内置工具数={len(builtin_tool_specs)} "
|
||||||
|
f"候选工具数={len(candidate_tool_specs)} "
|
||||||
|
f"最多保留候选数={max_keep}"
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
generation_result = await self._tool_filter_llm.generate_response(
|
||||||
|
prompt=filter_prompt,
|
||||||
|
options=LLMGenerationOptions(
|
||||||
|
temperature=0.0,
|
||||||
|
max_tokens=256,
|
||||||
|
response_format=RespFormat(
|
||||||
|
format_type=RespFormatType.JSON_SCHEMA,
|
||||||
|
schema=ToolFilterSelection,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning(f"工具预筛选失败,保留全部工具。错误={exc}")
|
||||||
|
return tool_specs
|
||||||
|
|
||||||
|
filtered_candidate_tool_specs = self._parse_tool_filter_response(
|
||||||
|
generation_result.response or "",
|
||||||
|
candidate_tool_specs,
|
||||||
|
max_keep,
|
||||||
|
)
|
||||||
|
if filtered_candidate_tool_specs is None:
|
||||||
|
logger.warning(
|
||||||
|
"工具预筛选返回结果无法解析,保留全部工具。"
|
||||||
|
f" 原始返回={generation_result.response or ''!r}"
|
||||||
|
)
|
||||||
|
return tool_specs
|
||||||
|
|
||||||
|
filtered_tool_specs = [*builtin_tool_specs, *filtered_candidate_tool_specs]
|
||||||
|
if not filtered_tool_specs:
|
||||||
|
logger.warning("工具预筛选得到空结果,保留全部工具以避免主流程失去工具能力。")
|
||||||
|
return tool_specs
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"工具预筛选完成: "
|
||||||
|
f"筛选前总数={len(tool_specs)} "
|
||||||
|
f"筛选后总数={len(filtered_tool_specs)} "
|
||||||
|
f"保留候选工具={[tool_spec.name for tool_spec in filtered_candidate_tool_specs]}"
|
||||||
|
)
|
||||||
|
return filtered_tool_specs
|
||||||
|
|
||||||
async def analyze_knowledge_need(
|
async def analyze_knowledge_need(
|
||||||
self,
|
self,
|
||||||
chat_history: List[LLMContextMessage],
|
chat_history: List[LLMContextMessage],
|
||||||
@@ -206,6 +470,15 @@ class MaisakaChatLoopService:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_role_badge_style(role: str) -> str:
|
def _get_role_badge_style(role: str) -> str:
|
||||||
|
"""返回终端中角色标签的样式。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
role: 消息角色名称。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Rich 可识别的样式字符串。
|
||||||
|
"""
|
||||||
|
|
||||||
if role == "system":
|
if role == "system":
|
||||||
return "bold white on blue"
|
return "bold white on blue"
|
||||||
if role == "user":
|
if role == "user":
|
||||||
@@ -218,6 +491,15 @@ class MaisakaChatLoopService:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_role_badge_label(role: str) -> str:
|
def _get_role_badge_label(role: str) -> str:
|
||||||
|
"""返回终端中角色标签的中文名称。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
role: 消息角色名称。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 用于展示的中文角色名称。
|
||||||
|
"""
|
||||||
|
|
||||||
if role == "system":
|
if role == "system":
|
||||||
return "系统"
|
return "系统"
|
||||||
if role == "user":
|
if role == "user":
|
||||||
@@ -230,6 +512,15 @@ class MaisakaChatLoopService:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _build_terminal_image_preview(image_base64: str) -> Optional[str]:
|
def _build_terminal_image_preview(image_base64: str) -> Optional[str]:
|
||||||
|
"""构造终端图片预览字符画。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image_base64: 图片的 Base64 编码。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Optional[str]: 生成成功时返回字符画文本,否则返回 ``None``。
|
||||||
|
"""
|
||||||
|
|
||||||
ascii_chars = " .:-=+*#%@"
|
ascii_chars = " .:-=+*#%@"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -257,6 +548,15 @@ class MaisakaChatLoopService:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _render_message_content(cls, content: Any) -> RenderableType:
|
def _render_message_content(cls, content: Any) -> RenderableType:
|
||||||
|
"""将消息内容渲染为终端可展示对象。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
content: 原始消息内容。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
RenderableType: Rich 可渲染对象。
|
||||||
|
"""
|
||||||
|
|
||||||
if isinstance(content, str):
|
if isinstance(content, str):
|
||||||
return Text(content)
|
return Text(content)
|
||||||
|
|
||||||
@@ -299,6 +599,15 @@ class MaisakaChatLoopService:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _format_tool_call_for_display(tool_call: Any) -> Dict[str, Any]:
|
def _format_tool_call_for_display(tool_call: Any) -> Dict[str, Any]:
|
||||||
|
"""将工具调用对象格式化为易读字典。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tool_call: 原始工具调用对象或字典。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict[str, Any]: 适合终端展示的工具调用字典。
|
||||||
|
"""
|
||||||
|
|
||||||
if isinstance(tool_call, dict):
|
if isinstance(tool_call, dict):
|
||||||
function_info = tool_call.get("function", {})
|
function_info = tool_call.get("function", {})
|
||||||
return {
|
return {
|
||||||
@@ -314,6 +623,17 @@ class MaisakaChatLoopService:
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _render_tool_call_panel(self, tool_call: Any, index: int, parent_index: int) -> Panel:
|
def _render_tool_call_panel(self, tool_call: Any, index: int, parent_index: int) -> Panel:
|
||||||
|
"""渲染单个工具调用面板。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tool_call: 原始工具调用对象。
|
||||||
|
index: 工具调用在当前消息中的序号。
|
||||||
|
parent_index: 所属消息的序号。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Panel: 工具调用展示面板。
|
||||||
|
"""
|
||||||
|
|
||||||
title = Text.assemble(
|
title = Text.assemble(
|
||||||
Text(" 工具调用 ", style="bold white on magenta"),
|
Text(" 工具调用 ", style="bold white on magenta"),
|
||||||
Text(f" #{parent_index}.{index}", style="muted"),
|
Text(f" #{parent_index}.{index}", style="muted"),
|
||||||
@@ -326,6 +646,16 @@ class MaisakaChatLoopService:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _render_message_panel(self, message: Any, index: int) -> Panel:
|
def _render_message_panel(self, message: Any, index: int) -> Panel:
|
||||||
|
"""渲染单条消息面板。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message: 原始消息对象或字典。
|
||||||
|
index: 消息序号。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Panel: 终端展示面板。
|
||||||
|
"""
|
||||||
|
|
||||||
if isinstance(message, dict):
|
if isinstance(message, dict):
|
||||||
raw_role = message.get("role", "unknown")
|
raw_role = message.get("role", "unknown")
|
||||||
content = message.get("content")
|
content = message.get("content")
|
||||||
@@ -377,17 +707,28 @@ class MaisakaChatLoopService:
|
|||||||
|
|
||||||
await self.ensure_chat_prompt_loaded()
|
await self.ensure_chat_prompt_loaded()
|
||||||
selected_history, selection_reason = self._select_llm_context_messages(chat_history)
|
selected_history, selection_reason = self._select_llm_context_messages(chat_history)
|
||||||
|
built_messages = self._build_request_messages(selected_history)
|
||||||
|
|
||||||
def message_factory(_client: BaseClient) -> List[Message]:
|
def message_factory(_client: BaseClient) -> List[Message]:
|
||||||
|
"""返回当前轮次已经构建好的请求消息。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
_client: 当前模型客户端;此处不依赖客户端能力。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Message]: 已经构建好的消息列表。
|
||||||
|
"""
|
||||||
|
|
||||||
del _client
|
del _client
|
||||||
return self._build_request_messages(selected_history)
|
return built_messages
|
||||||
|
|
||||||
all_tools: List[ToolDefinitionInput]
|
all_tools: List[ToolDefinitionInput]
|
||||||
if self._tool_registry is not None:
|
if self._tool_registry is not None:
|
||||||
all_tools = await self._tool_registry.get_llm_definitions()
|
tool_specs = await self._tool_registry.list_tools()
|
||||||
|
filtered_tool_specs = await self._filter_tool_specs_for_planner(selected_history, tool_specs)
|
||||||
|
all_tools = [tool_spec.to_llm_definition() for tool_spec in filtered_tool_specs]
|
||||||
else:
|
else:
|
||||||
all_tools = [*get_builtin_tools(), *self._extra_tools]
|
all_tools = [*get_builtin_tools(), *self._extra_tools]
|
||||||
built_messages = self._build_request_messages(selected_history)
|
|
||||||
|
|
||||||
ordered_panels: List[Panel] = []
|
ordered_panels: List[Panel] = []
|
||||||
for index, msg in enumerate(built_messages, start=1):
|
for index, msg in enumerate(built_messages, start=1):
|
||||||
@@ -454,7 +795,15 @@ class MaisakaChatLoopService:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _select_llm_context_messages(chat_history: List[LLMContextMessage]) -> tuple[List[LLMContextMessage], str]:
|
def _select_llm_context_messages(chat_history: List[LLMContextMessage]) -> tuple[List[LLMContextMessage], str]:
|
||||||
"""选择真正发送给 LLM 的上下文消息。"""
|
"""选择真正发送给 LLM 的上下文消息。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chat_history: 当前全部对话历史。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple[List[LLMContextMessage], str]: `(已选上下文, 选择说明)`。
|
||||||
|
"""
|
||||||
|
|
||||||
max_context_size = max(1, int(global_config.chat.max_context_size))
|
max_context_size = max(1, int(global_config.chat.max_context_size))
|
||||||
selected_indices: List[int] = []
|
selected_indices: List[int] = []
|
||||||
counted_message_count = 0
|
counted_message_count = 0
|
||||||
@@ -485,6 +834,15 @@ class MaisakaChatLoopService:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def build_chat_context(user_text: str) -> List[LLMContextMessage]:
|
def build_chat_context(user_text: str) -> List[LLMContextMessage]:
|
||||||
|
"""根据用户输入构造最小对话上下文。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_text: 用户输入文本。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[LLMContextMessage]: 构造好的上下文消息列表。
|
||||||
|
"""
|
||||||
|
|
||||||
timestamp = datetime.now()
|
timestamp = datetime.now()
|
||||||
visible_text = format_speaker_content(
|
visible_text = format_speaker_content(
|
||||||
global_config.maisaka.user_name.strip() or "用户",
|
global_config.maisaka.user_name.strip() or "用户",
|
||||||
|
|||||||
Reference in New Issue
Block a user