feat(agent): require web research consent
This commit is contained in:
+34
-3
@@ -15,6 +15,8 @@ import org.dromara.aihr.knowledge.service.AihrKnowledgeDataToolService.CurrentTa
|
||||
import org.dromara.aihr.knowledge.service.AihrKnowledgePrincipalResolver;
|
||||
import org.dromara.aihr.knowledge.service.AihrKnowledgeQueryService;
|
||||
import org.dromara.aihr.knowledge.service.AihrKnowledgeQueryService.MediaMode;
|
||||
import org.dromara.aihr.webai.AihrWebAiDto;
|
||||
import org.dromara.aihr.webai.AihrWebAiService;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
@@ -32,19 +34,26 @@ public class AihrAgentOrchestrator {
|
||||
private final AihrKnowledgePrincipalResolver principalResolver;
|
||||
private final AihrKnowledgeQueryService queryService;
|
||||
private final AihrAgentActionService actionService;
|
||||
private final AihrWebAiService webAiService;
|
||||
|
||||
public AgentResponse handle(AgentRequest request) {
|
||||
requireQuestion(request);
|
||||
AgentPlan plan = planner.plan(request.question(), false);
|
||||
policy.authorize(principalResolver.current(), plan, request.externalConsent());
|
||||
var principal = principalResolver.current();
|
||||
if (plan.requiresExternalConsent() && !request.externalConsent()) {
|
||||
return simple(plan, AgentStatus.NEEDS_INPUT,
|
||||
"全网查询会将脱敏后的问题发送给外部公开检索服务。",
|
||||
new Clarification("是否同意查询全网?", List.of()));
|
||||
}
|
||||
policy.authorize(principal, plan, request.externalConsent());
|
||||
return switch (plan.intent()) {
|
||||
case SOCIAL -> simple(plan, AgentStatus.COMPLETED, "你好,我是数字师傅。你可以问制度、待办、训练或现场问题。",
|
||||
null);
|
||||
case CLARIFY, DRAFT_ACTION, MEDIA_UNDERSTANDING -> simple(plan, AgentStatus.NEEDS_INPUT,
|
||||
"请再说明你想查询、记录或处理什么。",
|
||||
new Clarification("请补充具体目标", List.of("具体问题")));
|
||||
case WEB_RESEARCH -> simple(plan, AgentStatus.NEEDS_INPUT,
|
||||
"这项问题需要使用独立的全网查询能力。", new Clarification("请确认是否查询全网", List.of()));
|
||||
case WEB_RESEARCH -> fromWeb(plan, request,
|
||||
webAiService.query(new AihrWebAiDto.QueryRequest(plan.rewrittenRequest(), true)));
|
||||
default -> fromQuery(plan, request);
|
||||
};
|
||||
}
|
||||
@@ -99,6 +108,28 @@ public class AihrAgentOrchestrator {
|
||||
);
|
||||
}
|
||||
|
||||
private static AgentResponse fromWeb(AgentPlan plan, AgentRequest request,
|
||||
AihrWebAiDto.QueryResponse response) {
|
||||
AgentStatus status = switch (response.status()) {
|
||||
case "OK" -> AgentStatus.COMPLETED;
|
||||
case "NO_SOURCES", "NO_ANSWER" -> AgentStatus.NO_EVIDENCE;
|
||||
case "CONSENT_REQUIRED" -> AgentStatus.NEEDS_INPUT;
|
||||
default -> AgentStatus.UNAVAILABLE;
|
||||
};
|
||||
String answer = response.answer() == null || response.answer().isBlank()
|
||||
? response.notice()
|
||||
: response.answer();
|
||||
return new AgentResponse(
|
||||
runId(), request.conversationId(), request.contextVersion(), plan.intent(), status, answer,
|
||||
List.of(new SourceSummary("WEB", response.provider(), OffsetDateTime.now().toString())),
|
||||
List.of(), List.of(), response, null,
|
||||
status == AgentStatus.NEEDS_INPUT
|
||||
? new Clarification("是否同意查询全网?", List.of())
|
||||
: null,
|
||||
List.of()
|
||||
);
|
||||
}
|
||||
|
||||
private static void requireQuestion(AgentRequest request) {
|
||||
if (request == null || request.question() == null || request.question().isBlank()) {
|
||||
throw new ServiceException("问题不能为空", 400);
|
||||
|
||||
+1
@@ -54,6 +54,7 @@ class AihrAgentMediaTest {
|
||||
}
|
||||
},
|
||||
query,
|
||||
null,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
+2
-1
@@ -110,7 +110,8 @@ class AihrAgentOrchestratorTest {
|
||||
}
|
||||
},
|
||||
query,
|
||||
actionService
|
||||
actionService,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
package org.dromara.aihr.agent;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.dromara.aihr.agent.AihrAgentDto.AgentRequest;
|
||||
import org.dromara.aihr.agent.AihrAgentDto.AgentStatus;
|
||||
import org.dromara.aihr.knowledge.domain.AihrKnowledgePrincipal;
|
||||
import org.dromara.aihr.knowledge.domain.AihrKnowledgeQueryDto.QueryRequest;
|
||||
import org.dromara.aihr.knowledge.domain.AihrKnowledgeQueryDto.QueryResponse;
|
||||
import org.dromara.aihr.knowledge.service.AihrKnowledgePrincipalResolver;
|
||||
import org.dromara.aihr.knowledge.service.AihrKnowledgeQueryService;
|
||||
import org.dromara.aihr.webai.AihrWebAiDto;
|
||||
import org.dromara.aihr.webai.AihrWebAiService;
|
||||
import org.dromara.aihr.webai.AihrWebSearchClient;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@Tag("dev")
|
||||
class AihrAgentWebResearchTest {
|
||||
|
||||
@Test
|
||||
void missingConsentReturnsConsentCardWithoutExternalCall() {
|
||||
var web = new FakeWebService();
|
||||
|
||||
var result = orchestrator(web).handle(request(false));
|
||||
|
||||
assertEquals(AgentStatus.NEEDS_INPUT, result.status());
|
||||
assertEquals(0, web.calls);
|
||||
assertTrue(result.citations().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void consentedResearchReturnsOnlySeparatedWebData() {
|
||||
var web = new FakeWebService();
|
||||
|
||||
var result = orchestrator(web).handle(request(true));
|
||||
|
||||
assertEquals(AgentStatus.COMPLETED, result.status());
|
||||
assertEquals(1, web.calls);
|
||||
assertTrue(result.citations().isEmpty());
|
||||
assertEquals("WEB", result.sourceSummary().get(0).type());
|
||||
var data = (AihrWebAiDto.QueryResponse) result.data();
|
||||
assertEquals("https://example.com/rule", data.sources().get(0).url());
|
||||
}
|
||||
|
||||
private static AgentRequest request(boolean consent) {
|
||||
return new AgentRequest("帮我查全网,国家对高空抛物怎么规定", "conversation_1", 0L,
|
||||
"P1", null, consent);
|
||||
}
|
||||
|
||||
private static AihrAgentOrchestrator orchestrator(FakeWebService web) {
|
||||
var principal = new AihrKnowledgePrincipal("000000", 1L, "app_user", "employee-1",
|
||||
Set.of("employee"), Set.of("P1"), "app");
|
||||
return new AihrAgentOrchestrator(
|
||||
new AihrAgentPlanner(null, new ObjectMapper()),
|
||||
new AihrAgentPolicy(),
|
||||
new AihrKnowledgePrincipalResolver(null) {
|
||||
@Override
|
||||
public AihrKnowledgePrincipal current() {
|
||||
return principal;
|
||||
}
|
||||
},
|
||||
new AihrKnowledgeQueryService(null, null, null, null, null, null, null, null, null) {
|
||||
@Override
|
||||
public QueryResponse queryInternal(QueryRequest request) {
|
||||
throw new AssertionError("全网查询不得进入企业知识检索");
|
||||
}
|
||||
},
|
||||
null,
|
||||
web
|
||||
);
|
||||
}
|
||||
|
||||
private static final class FakeWebService extends AihrWebAiService {
|
||||
private int calls;
|
||||
|
||||
private FakeWebService() {
|
||||
super(null, null, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AihrWebAiDto.QueryResponse query(AihrWebAiDto.QueryRequest request) {
|
||||
calls++;
|
||||
return new AihrWebAiDto.QueryResponse(
|
||||
"web", "OK", "公开规定要求依法承担相应责任。",
|
||||
List.of(new AihrWebSearchClient.WebSource(
|
||||
"公开规定", "https://example.com/rule", "法规摘要", 0.9)),
|
||||
"test-provider", "回答来自全网公开资料,不代表企业内部 SOP", true, true
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user