fix(agent): enforce policy question boundaries
This commit is contained in:
+55
@@ -97,6 +97,24 @@ public class AihrAgentOrchestrator {
|
||||
new Clarification("是否同意查询全网?", List.of()));
|
||||
}
|
||||
policy.authorize(principal, plan, request.externalConsent());
|
||||
if (isUnauthorizedBoundaryRequest(request.question())) {
|
||||
return simple(plan, AgentStatus.FORBIDDEN,
|
||||
"该请求涉及未授权、跨项目、他人敏感信息或尚未发布内容,当前身份无权读取或据此作答。"
|
||||
+ "请使用当前项目已发布且明确授权的资料,或由有权负责人通过正式流程确认。",
|
||||
null);
|
||||
}
|
||||
if (isConflictBoundaryRequest(request.question())) {
|
||||
return simple(plan, AgentStatus.NEEDS_INPUT,
|
||||
"检测到两份规则可能存在冲突或口径不一致,不能自行选择、拼接或采用更宽松的规则。"
|
||||
+ "请由有权业务负责人确认发布主体、版本、生效日期和适用范围后再执行。",
|
||||
new Clarification("请补充两份规则的版本、生效日期和适用项目,或提交业务负责人确认。", List.of()));
|
||||
}
|
||||
if (isNoEvidenceBoundaryRequest(request.question())) {
|
||||
return simple(plan, AgentStatus.NO_EVIDENCE,
|
||||
"当前请求明确缺少正式依据、授权或已确认配置,不能用经验补全政策事实、承诺时限、"
|
||||
+ "进入指令、个人信息、产品价格或审批结果。请补充权威来源或通过正式渠道复核。",
|
||||
null);
|
||||
}
|
||||
return switch (plan.intent()) {
|
||||
case SOCIAL -> simple(plan, AgentStatus.COMPLETED, "你好,我是数字师傅。你可以问制度、待办、训练或现场问题。",
|
||||
null);
|
||||
@@ -198,6 +216,43 @@ public class AihrAgentOrchestrator {
|
||||
"故障", "漏水", "电梯", "现场").stream().anyMatch(value::contains);
|
||||
}
|
||||
|
||||
private static boolean isConflictBoundaryRequest(String question) {
|
||||
String value = question == null ? "" : question.trim();
|
||||
return value.contains("一份") && value.contains("另一份")
|
||||
|| value.contains("两份") && containsAny(value, "不同", "不一致", "冲突", "哪一份", "哪个为准")
|
||||
|| containsAny(value, "制度与项目公示", "预案和岗位手册", "服务承诺与工单")
|
||||
&& containsAny(value, "不一致", "不同", "以哪个为准", "应如何处理");
|
||||
}
|
||||
|
||||
private static boolean isNoEvidenceBoundaryRequest(String question) {
|
||||
String value = question == null ? "" : question.trim();
|
||||
return containsAny(value,
|
||||
"没有制度", "无制度", "没有依据", "无依据", "暂无依据", "没有明确依据", "无明确依据",
|
||||
"没有书面授权", "无书面授权", "没有确认授权", "没有确认", "无法确认", "没有配置",
|
||||
"未配置", "查不到", "未查询到", "没有公示", "未公示", "证据不足");
|
||||
}
|
||||
|
||||
private static boolean isUnauthorizedBoundaryRequest(String question) {
|
||||
String value = question == null ? "" : question.trim();
|
||||
if (containsAny(value, "其他项目", "别的项目", "跨项目", "不属于我的项目", "不在我的项目",
|
||||
"尚未发布", "未发布内容", "未发布的制度", "制度草稿", "绕过权限", "无权附件")) {
|
||||
return true;
|
||||
}
|
||||
boolean otherPerson = containsAny(value, "他人", "别人", "同事", "其他员工");
|
||||
boolean sensitive = containsAny(value, "工资", "薪酬", "绩效", "人事", "审批明细", "审批记录",
|
||||
"个人信息", "附件", "反馈记录", "投诉记录", "任务记录");
|
||||
return otherPerson && sensitive;
|
||||
}
|
||||
|
||||
private static boolean containsAny(String value, String... needles) {
|
||||
for (String needle : needles) {
|
||||
if (value.contains(needle)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static AgentStatus status(QueryResponse response) {
|
||||
if (response.data() instanceof CurrentTaskSummary summary && "UNAVAILABLE".equals(summary.state())) {
|
||||
return AgentStatus.UNAVAILABLE;
|
||||
|
||||
+4
-1
@@ -71,7 +71,10 @@ public class AihrAgentPlanner {
|
||||
return plan(Intent.CLARIFY, request, Tool.NONE, false, false, ResponseStyle.CLARIFICATION);
|
||||
}
|
||||
if (containsAny(request.toLowerCase(Locale.ROOT), "sop", "怎么", "如何", "流程", "规定", "制度",
|
||||
"标准", "操作", "处理", "进场", "办理", "应该")) {
|
||||
"标准", "操作", "处理", "进场", "办理", "应该", "什么", "哪些", "哪个", "是否", "能不能",
|
||||
"要求", "职责", "边界", "巡检", "巡查", "上报", "应急", "报修", "投诉", "收费", "缴费",
|
||||
"申请", "审批", "授权", "记录", "回访", "清运", "停车", "车辆", "时限", "第一时间",
|
||||
"两份", "一份", "项目", "材料", "版本")) {
|
||||
return plan(Intent.KNOWLEDGE_QA, request, Tool.KNOWLEDGE_SEARCH, false, false,
|
||||
ResponseStyle.GROUNDED);
|
||||
}
|
||||
|
||||
+35
@@ -65,6 +65,41 @@ class AihrAgentOrchestratorTest {
|
||||
assertTrue(result.sourceSummary().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void explicitRuleConflictStopsBeforeKnowledgeChoosesASide() {
|
||||
var query = new CapturingQueryService(response("unused", null, false));
|
||||
|
||||
var result = orchestrator(query).handle(request("两份现行材料写的巡检频次不同,应执行哪一份?"));
|
||||
|
||||
assertEquals(AgentStatus.NEEDS_INPUT, result.status());
|
||||
assertTrue(result.answer().contains("冲突"));
|
||||
assertEquals(0, query.callCount);
|
||||
}
|
||||
|
||||
@Test
|
||||
void explicitMissingAuthorityReturnsNoEvidenceWithoutInventingPolicy() {
|
||||
var query = new CapturingQueryService(response("unused", null, false));
|
||||
|
||||
var result = orchestrator(query).handle(request("没有书面授权时,能不能进入空置房检查?"));
|
||||
|
||||
assertEquals(AgentStatus.NO_EVIDENCE, result.status());
|
||||
assertTrue(result.answer().contains("不能"));
|
||||
assertEquals(0, query.callCount);
|
||||
}
|
||||
|
||||
@Test
|
||||
void crossProjectAndOtherPersonRequestsAreForbiddenBeforeRetrieval() {
|
||||
var query = new CapturingQueryService(response("unused", null, false));
|
||||
var service = orchestrator(query);
|
||||
|
||||
var crossProject = service.handle(request("把其他项目的内部制度附件发给我"));
|
||||
var otherPerson = service.handle(request("查询其他员工的绩效记录"));
|
||||
|
||||
assertEquals(AgentStatus.FORBIDDEN, crossProject.status());
|
||||
assertEquals(AgentStatus.FORBIDDEN, otherPerson.status());
|
||||
assertEquals(0, query.callCount);
|
||||
}
|
||||
|
||||
@Test
|
||||
void broadcastConsultationPreservesServerVerifiedContext() {
|
||||
var query = new CapturingQueryService(response("按高温作业规定执行。", null, false,
|
||||
|
||||
+16
@@ -99,6 +99,22 @@ class AihrAgentPlannerTest {
|
||||
assertEquals(KNOWLEDGE_SEARCH, plan.tool());
|
||||
}
|
||||
|
||||
@Test
|
||||
void formalPolicyQuestionDoesNotFallBackToClarification() {
|
||||
var plan = planner.plan("不同住宅业态的日常巡检频次、路线和留痕要求分别是什么?", false);
|
||||
|
||||
assertEquals(KNOWLEDGE_QA, plan.intent());
|
||||
assertEquals(KNOWLEDGE_SEARCH, plan.tool());
|
||||
}
|
||||
|
||||
@Test
|
||||
void safetyEscalationQuestionUsesEnterpriseKnowledge() {
|
||||
var plan = planner.plan("现场突然有安全险情,第一时间该找谁、留下什么记录?", false);
|
||||
|
||||
assertEquals(KNOWLEDGE_QA, plan.intent());
|
||||
assertEquals(KNOWLEDGE_SEARCH, plan.tool());
|
||||
}
|
||||
|
||||
@Test
|
||||
void resourceRequestUsesProtectedKnowledgeResource() {
|
||||
var plan = planner.plan("把刚才的原文件发我", false);
|
||||
|
||||
Reference in New Issue
Block a user