fix(aibox): inject available model profile names into create_subagent (M2)

The LLM had no way to know which profile names exist in the models
config section; a hallucinated name fell through to bare-name semantics
(default endpoint + bogus model) and failed at request time with API
400.

SubAgentTool now builds the model hint dynamically from config via
_model_names_hint(): description and the model parameter's JSON Schema
description both list the profile names, with the default profile
(api.default_model) marked. Empty or malformed models sections keep
the original wording untouched (zero breakage); exceptions degrade to
no hint.

README: tool table note, test count 293, new v3.4 changelog entry
summarizing the H1/H2/H3/M2 profile-hardening batch.

Tests: new TestM2SubAgentModelHint (5 cases); full suite 293 passed.
This commit is contained in:
qing committed 2026-08-17 23:42:04 +08:00
1 parent 84a3f6dd5e
commit 77262f8c3a
3 files changed
+89 -5

No files matched your search

+51
View File
@@ -388,6 +388,57 @@ class TestH3ModelPlumbing:
assert args.model == "kimi"
# ==================== M2:create_subagent 工具注入档案名列表 ====================
class TestM2SubAgentModelHint:
"""M2:LLM 此前不知道合法档案名,幻觉名走裸名语义(默认端点+错误模型)→ API 400。
验证 SubAgentTool 的 description 与 model 参数 description 动态携带
models 节档案名列表(默认档案标注),models 为空时保持原样(零破坏)。
"""
def _tool_with(self, models, default_model="deepseek"):
from aibox import SubAgentTool
tool = SubAgentTool(logger=None)
tool.config = FakeConfig({
"models": models or {},
"api": {"default_model": default_model},
})
return tool
def test_description_lists_profiles(self):
"""工具描述包含全部档案名"""
tool = self._tool_with(MODELS)
desc = tool.get_description()
assert "kimi" in desc and "local" in desc and "deepseek" in desc
assert "可用模型档案" in desc
def test_default_profile_marked(self):
"""默认档案带(默认)标注"""
tool = self._tool_with(MODELS, default_model="kimi")
desc = tool.get_description()
assert "kimi(默认)" in desc
def test_param_schema_lists_profiles(self):
"""JSON Schema 的 model 参数 description 同样携带档案名"""
tool = self._tool_with(MODELS)
model_desc = tool.get_parameters()["properties"]["model"]["description"]
assert "kimi" in model_desc and "deepseek" in model_desc
def test_empty_models_keeps_original(self):
"""models 为空 → 描述保持原文案,不含提示语(零破坏)"""
tool = self._tool_with({})
desc = tool.get_description()
assert "可用模型档案" not in desc
model_desc = tool.get_parameters()["properties"]["model"]["description"]
assert model_desc == "子代理使用的模型名称,默认与主代理一致"
def test_non_dict_models_tolerated(self):
"""models 节类型异常(如字符串)→ 容错返回空提示"""
tool = self._tool_with("not-a-dict")
assert "可用模型档案" not in tool.get_description()
# ==================== DeepSeekChat 档案级 reasoning 读取 ====================
class TestChatReasoningFlag: