- IntentShortcut 泛化成规则表(Rule{短语集 → Intent}):加新意图只需
① 一条 Intent ② 一个短语集 ③ RULES.add(...),不用动 AgentOrchestrator。
新增 NOTICE_PUBLISH —— 仅教职工(notice.publish),学生无落点则落回模型如实说明。
短语表只收带「发/发布」动词的说法(发布公告/发个公告/发布校园公告/发通知…),
光说「公告」「通知」更像查询,不收,避免误判。
- ★ 修 prepareOptionCall 漏拼字典工具的 fixed 参数:公文部门字典是
/api/dict/List?type=1,丢了 type 前端取回的是「没指定类型」的结果 → 空下拉。
- deptId 补 optionsKeys("k","v"):平台通用字典返回 {k,v} 而非 id/name;
服务端兜底 fetchDictOptions 的候选键也补上 k/v。
- ★ 移除 notice.publish 的 publishType 与 deptName:后端 /api/notice/Create 就是
直接发布(无 publishType 字段,是否转审批由学校「应用设置」绑定的流程决定),
声明它会让用户以为选了「存草稿」却仍然直接发出;deptName 由后端按 deptId 反查字典。
- system prompt 加第 10 条说明。
- 测试:新增 NoticeIntentFormTest(7),IntentDirectFormTest 加教师/学生两条公告用例
(仍用「一调就炸」的假 LLM 证明不过大模型);全量 71/71。
This commit is contained in:
@@ -38,7 +38,13 @@ import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* 「我要请假」在编排层的端到端断言:**同一句话、不同身份 → 不同表单**,且**不经过大模型**。
|
||||
* 裸意图在编排层的端到端断言:**同一句话、不同身份 → 不同表单**,且**不经过大模型**。
|
||||
*
|
||||
* <p>覆盖两条已落地的意图:
|
||||
* <ul>
|
||||
* <li>「我要请假」→ 学生拿请假申请、教职工拿请假登记(帮学生请假);</li>
|
||||
* <li>「我要发布校园公告」→ 教职工拿发布表单,学生落回模型。</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>这是本次修复的核心承诺:用户说「我要请假」应当**直接**看到表单,
|
||||
* 而不是先被模型拿去查字典、或自行编造时间直接提交、或反过来追问用户。
|
||||
@@ -70,6 +76,7 @@ class IntentDirectFormTest {
|
||||
void setUp() {
|
||||
settings = new AgentSettings();
|
||||
settings.getModules().put("homeleave", "http://base.yunhaixingtong.com/homeleave");
|
||||
settings.getModules().put("campusnotice", "http://base.yunhaixingtong.com/campusnotice");
|
||||
registry = new ToolRegistry(settings);
|
||||
registry.init();
|
||||
|
||||
@@ -166,6 +173,39 @@ class IntentDirectFormTest {
|
||||
assertNotNull(field(resp, "remarks"));
|
||||
}
|
||||
|
||||
/* ==================== 教师:发布校园公告 ==================== */
|
||||
|
||||
@Test
|
||||
void teacherSayingPublishNoticeGetsPublishFormWithoutCallingLlm() {
|
||||
ChatResponse resp = chat(EXPLODING_LLM, "我要发布校园公告", caller(AgentRole.TEACHER));
|
||||
|
||||
assertNotNull(resp.getForm(), "应直接下发表单");
|
||||
assertEquals("notice.publish", resp.getForm().getTool());
|
||||
assertTrue(resp.getForm().isWrite(), "是写操作,前端要二次确认");
|
||||
assertEquals("/list/new", resp.getForm().getLandingPath());
|
||||
assertTrue(resp.getReply().contains("发布"), "引导语应说明这是发布,实际:" + resp.getReply());
|
||||
|
||||
// 公告表单:标题(单行)+ 正文(多行)+ 发文部门(下拉)
|
||||
assertEquals("text", field(resp, "title").getWidget());
|
||||
assertEquals("textarea", field(resp, "content").getWidget());
|
||||
|
||||
FormField deptId = field(resp, "deptId");
|
||||
assertEquals("select", deptId.getWidget(), "发文部门应是下拉,不该让用户手填部门编号");
|
||||
assertNotNull(deptId.getOptionCall(), "发文部门下拉应带上「代取地址」");
|
||||
assertEquals("/campusnotice/api/dict/List?type=1", deptId.getOptionCall().getPathAndQuery(),
|
||||
"字典地址必须带 type=1,否则取不回发文部门");
|
||||
assertEquals("k", deptId.getValueKey());
|
||||
assertEquals("v", deptId.getLabelKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
void studentSayingPublishNoticeFallsThroughToModel() {
|
||||
// 学生没有发布公告能力 → 不直达,交给模型如实说明需要教师/管理员身份
|
||||
ChatResponse resp = chat(ECHO_LLM, "我要发布校园公告", caller(AgentRole.STUDENT));
|
||||
assertNull(resp.getForm(), "学生不该拿到发布公告表单");
|
||||
assertEquals("(模型回复)", resp.getReply());
|
||||
}
|
||||
|
||||
/* ==================== 不该被劫持的情况 ==================== */
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user