feat(notice): 「发布校园公告」裸意图直达表单 + 修发文部门下拉与假的发布方式
build / build-push (push) Successful in 1m9s

- 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:
lijing
2026-10-12 18:54:15 +08:00
parent c11d331371
commit d2c6c185b5
7 changed files with 352 additions and 47 deletions
@@ -113,8 +113,8 @@ public class AgentOrchestrator {
ChatResponse resp = new ChatResponse();
resp.setSessionId(session.getSessionId());
// ★ 裸意图直达:「我要请假」这类只有意图、没有细节的句子,直接按身份弹表单,
// 不交给大模型决策(模型可能先去查字典、或自行编造时间直接提交,都不合预期)。
// ★ 裸意图直达:「我要请假」「发布校园公告」这类只有意图、没有细节的句子,直接按身份弹表单,
// 不交给大模型决策(模型可能先去查字典、或自行编造参数直接提交,都不合预期)。
ChatResponse direct = directForm(req.getMessage(), caller, authorizedModIds, roles, session, out);
if (direct != null) {
return direct;
@@ -408,8 +408,9 @@ public class AgentOrchestrator {
/* ==================== 表单:缺参拦截 与 直提交 ==================== */
/**
* 裸意图直达:命中「只有意图、没有细节」的句子(如「我要请假」)时,
* 按当前身份直接构建表单返回 —— 学生拿「请假申请」,教职工拿「请假登记(帮学生请假)」。
* 裸意图直达:命中「只有意图、没有细节」的句子(如「我要请假」「发布校园公告」)时,
* 按当前身份直接构建表单返回 —— 请假:学生拿「请假申请」、教职工拿「请假登记(帮学生请假)」;
* 发布公告:只有教职工能直达,学生落回模型(由模型的权限准则如实说明)。
*
* <p>判据与能力目录 / 模型侧过滤**完全一致**({@code visibleTools}),
* 因此不存在「直达出一个本身份无权使用的表单」的可能;不命中或不适用就返回 {@code null},
@@ -449,8 +450,8 @@ public class AgentOrchestrator {
resp.setForm(form);
resp.setReply(reply);
out.emit(AgentEvent.of("form", "需要补充信息:" + String.join("、", form.getMissing())));
log.info("意图直达弹表单:session={} 身份={} 工具={}", session.getSessionId(),
staff ? "教职工" : "学生", def.getName());
log.info("意图直达弹表单:session={} 意图={} 身份={} 工具={}", session.getSessionId(),
intent.getName(), staff ? "教职工" : "学生", def.getName());
finish(resp, session, 0, 0, out);
return resp;
}
@@ -811,11 +812,12 @@ public class AgentOrchestrator {
continue;
}
JSONObject o = (JSONObject) item;
String value = firstOf(o, "id", "value", "code", "dictId", "typeId", "key");
// "k"/"v" 是平台通用字典(/api/dict/List)的形态,与业务 VO(classId/className)并存
String value = firstOf(o, "id", "value", "code", "dictId", "typeId", "key", "k");
if (StringUtils.isBlank(value)) {
continue;
}
String label = firstOf(o, "name", "label", "text", "title", "dictName", "value");
String label = firstOf(o, "name", "label", "text", "title", "dictName", "v", "value");
Map<String, String> one = new LinkedHashMap<>();
one.put("value", value);
one.put("label", StringUtils.isBlank(label) ? value : label);
@@ -1007,6 +1009,10 @@ public class AgentOrchestrator {
.append("「帮学生登记请假」= 教师给学生登记(免审直接生效,必须先选班级与学生)。")
.append("下发给你的工具已按当前身份过滤,按标题选即可,不要互换。\n");
sb.append("10. ★ 「发布校园公告」是**立即发布、全站可见**的动作:必须拿到标题、正文、发文部门三项;")
.append("缺哪项就照常调用工具让系统弹表单,不要自己编造发文部门,也不要用文字反复追问。")
.append("该工具只对教职工下发(学生身份看不到),学生要求发布公告时如实说明身份不够。\n");
return sb.toString();
}