feat(harness): add rag and log projections

This commit is contained in:
zhuyongxin
2026-07-21 20:12:18 +08:00
parent 0dbdd7d8d3
commit 3e602781d6
20 changed files with 1140 additions and 0 deletions
@@ -0,0 +1,132 @@
package com.superbiz.agent.harness.tool.adapter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.superbiz.agent.harness.contract.EvidenceStatus;
import com.superbiz.agent.harness.core.DiagnosisHarnessCore;
import com.superbiz.agent.harness.core.HarnessCoreFixtures;
import com.superbiz.agent.harness.core.MutableClock;
import com.superbiz.agent.harness.core.RunContext;
import com.superbiz.agent.harness.tool.boundary.ProjectedToolResult;
import com.superbiz.agent.harness.tool.boundary.ToolBoundary;
import com.superbiz.agent.harness.tool.boundary.ToolBoundaryResult;
import com.superbiz.agent.harness.tool.boundary.ToolCallRequestEnvelope;
import com.superbiz.agent.harness.tool.contract.LogQueryScope;
import com.superbiz.agent.harness.tool.projection.QueryLogsResultProjector;
import com.superbiz.agent.harness.tool.projection.RagResultProjector;
import com.superbiz.agent.harness.tool.projection.ToolProjectionLimits;
import com.superbiz.agent.harness.tool.store.CanonicalInvocationLimits;
import com.superbiz.agent.harness.tool.store.CanonicalInvocationStore;
import com.superbiz.agent.harness.tool.store.CanonicalToolInvocation;
import com.superbiz.agent.harness.tool.store.DuplicateInvocationException;
import com.superbiz.agent.harness.tool.store.ToolCallKeyFactory;
import org.junit.jupiter.api.Test;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class ToolAdapterTest {
private final ObjectMapper objectMapper = new ObjectMapper();
@Test
void ragAdapterUsesFrameworkIdAndCanonicalBoundary() throws Exception {
MutableClock clock = new MutableClock(Instant.parse("2026-07-21T10:00:00Z"));
FakeStore store = new FakeStore();
ToolBoundary boundary = boundary(clock, store);
RagToolAdapter adapter = new RagToolAdapter(boundary, objectMapper,
new RagResultProjector(objectMapper, ToolProjectionLimits.defaults()),
query -> Map.of("found", true, "evidenceBlocks", java.util.List.of(
Map.of("source", "doc-1", "content", "exact excerpt"))));
RunContext context = HarnessCoreFixtures.core(clock).startRun("session", "run-rag");
ToolBoundaryResult result = adapter.execute(context, envelope("run-rag", "framework-rag-1",
"{\"query\":\"timeout\"}"));
assertEquals("framework-rag-1", result.toolCallId());
assertEquals(EvidenceStatus.EVIDENCE_FOUND, result.evidenceStatus());
assertTrue(result.agentResult().contains("exact excerpt"));
assertEquals("framework-rag-1", store.records.values().iterator().next().toolCallId());
}
@Test
void queryLogsAdapterInjectsLegacyControlsAndPreservesLogicalScope() throws Exception {
MutableClock clock = new MutableClock(Instant.parse("2026-07-21T10:00:00Z"));
FakeStore store = new FakeStore();
AtomicReference<String> topic = new AtomicReference<>();
AtomicReference<String> region = new AtomicReference<>();
ToolBoundary boundary = boundary(clock, store);
QueryLogsToolAdapter adapter = new QueryLogsToolAdapter(boundary, objectMapper,
new QueryLogsResultProjector(objectMapper, ToolProjectionLimits.defaults()),
(actualRegion, actualTopic, query, limit) -> {
region.set(actualRegion);
topic.set(actualTopic);
return "{\"success\":true,\"total\":1,\"logs\":[{\"timestamp\":\"2026-07-21 09:59:00\",\"level\":\"WARN\",\"service\":\"order-service\",\"message\":\"timeout\"}]}";
}, clock);
RunContext context = HarnessCoreFixtures.core(clock).startRun("session", "run-log");
ToolBoundaryResult result = adapter.execute(context, envelope("run-log", "framework-log-1",
"{\"topic\":\"APPLICATION\",\"query\":\"timeout\",\"lookback_minutes\":30}"));
assertEquals("ap-guangzhou", region.get());
assertEquals("application-logs", topic.get());
assertEquals(EvidenceStatus.EVIDENCE_FOUND, result.evidenceStatus());
assertTrue(result.agentResult().contains("2026-07-21T09:30:00Z"));
assertTrue(result.agentResult().contains("2026-07-21T10:00:00Z"));
assertEquals("framework-log-1", result.toolCallId());
}
private ToolBoundary boundary(MutableClock clock, FakeStore store) {
DiagnosisHarnessCore core = HarnessCoreFixtures.core(clock);
return new ToolBoundary(core, new ToolCallKeyFactory("superbiz:harness:tool-call"),
store, objectMapper, clock);
}
private ToolCallRequestEnvelope envelope(String runId, String toolCallId, String request) {
return new ToolCallRequestEnvelope(runId, toolCallId, "tool", request, true, true);
}
private static final class FakeStore implements CanonicalInvocationStore {
private final CanonicalInvocationLimits limits = new CanonicalInvocationLimits(Duration.ofHours(1), 20_000, 10_000);
private final Map<String, CanonicalToolInvocation> records = new HashMap<>();
@Override
public CanonicalInvocationLimits limits() {
return limits;
}
@Override
public void begin(String key, CanonicalToolInvocation invocation) {
if (records.putIfAbsent(key, invocation) != null) {
throw new DuplicateInvocationException();
}
}
@Override
public Optional<CanonicalToolInvocation> find(String key) {
return Optional.ofNullable(records.get(key));
}
@Override
public CanonicalToolInvocation markReady(String key, String rawResponse, String agentResult,
EvidenceStatus evidenceStatus, Instant completedAt) {
CanonicalToolInvocation updated = records.get(key).markReady(rawResponse, agentResult, evidenceStatus, completedAt);
records.put(key, updated);
return updated;
}
@Override
public CanonicalToolInvocation markError(String key, String rawResponse, String errorCode, Instant completedAt) {
CanonicalToolInvocation updated = records.get(key).markError(rawResponse, errorCode, completedAt);
records.put(key, updated);
return updated;
}
}
}
@@ -0,0 +1,60 @@
package com.superbiz.agent.harness.tool.projection;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.superbiz.agent.harness.contract.EvidenceStatus;
import com.superbiz.agent.harness.tool.boundary.ProjectedToolResult;
import com.superbiz.agent.harness.tool.contract.LogQueryScope;
import com.superbiz.agent.harness.tool.contract.LogTopic;
import com.superbiz.agent.harness.tool.contract.QueryLogsRequest;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class QueryLogsResultProjectorTest {
private final ObjectMapper objectMapper = new ObjectMapper();
private final LogQueryScope scope = new LogQueryScope(LogTopic.APPLICATION, "timeout",
"2026-07-21T10:00:00Z", "2026-07-21T10:30:00Z");
@Test
void projectsMockScopePatternsAndRedactsSensitiveFields() throws Exception {
QueryLogsResultProjector projector = new QueryLogsResultProjector(objectMapper,
new ToolProjectionLimits(2, 1200, 2, 2, 200, 100, 4096));
String raw = """
{"success":true,"total":3,"logs":[
{"timestamp":"2026-07-21 10:29:00","level":"ERROR","service":"order-service","instance":"pod-secret-1","message":"password=abc token:xyz pod-order-1 10.0.0.1 SELECT * FROM x WHERE name='alice' at a.B.c(A.java:1)","metrics":{"secret":"x"}},
{"timestamp":"2026-07-21 10:28:00","level":"ERROR","service":"order-service","instance":"pod-secret-2","message":"password=def token:qwe pod-order-2 10.0.0.2 SELECT * FROM x WHERE name='bob'"},
{"timestamp":"2026-07-21 10:27:00","level":"WARN","service":"order-service","message":"other"}]}
""";
ProjectedToolResult projected = projector.project(new QueryLogsRequest(LogTopic.APPLICATION, "timeout", 30),
"framework-log-1", scope, raw);
JsonNode json = objectMapper.readTree(projected.agentResult());
assertEquals(EvidenceStatus.EVIDENCE_FOUND, projected.evidenceStatus());
assertEquals("MOCK", json.path("source_kind").asText());
assertEquals("APPLICATION", json.path("scope").path("topic").asText());
assertEquals(3, json.path("match_count").asInt());
assertTrue(json.path("truncated").asBoolean());
assertFalse(projected.agentResult().contains("abc"));
assertFalse(projected.agentResult().contains("xyz"));
assertFalse(projected.agentResult().contains("pod-order"));
assertFalse(projected.agentResult().contains("10.0.0.1"));
assertFalse(projected.agentResult().contains("metrics"));
assertFalse(projected.agentResult().contains("instance"));
}
@Test
void returnsNoEvidenceForSuccessfulEmptyLogs() throws Exception {
QueryLogsResultProjector projector = new QueryLogsResultProjector(objectMapper, ToolProjectionLimits.defaults());
ProjectedToolResult projected = projector.project(new QueryLogsRequest(LogTopic.SYSTEM_EVENTS, "missing", 5),
"framework-log-2", scope, "{\"success\":true,\"total\":0,\"logs\":[]}");
assertEquals(EvidenceStatus.NO_EVIDENCE, projected.evidenceStatus());
assertEquals(0, objectMapper.readTree(projected.agentResult()).path("events").size());
}
}
@@ -0,0 +1,53 @@
package com.superbiz.agent.harness.tool.projection;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.superbiz.agent.harness.contract.EvidenceStatus;
import com.superbiz.agent.harness.tool.boundary.ProjectedToolResult;
import com.superbiz.agent.harness.tool.contract.RagToolRequest;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class RagResultProjectorTest {
private final ObjectMapper objectMapper = new ObjectMapper();
@Test
void projectsOnlyDeduplicatedBoundedEvidence() throws Exception {
RagResultProjector projector = new RagResultProjector(objectMapper,
new ToolProjectionLimits(2, 8, 2, 2, 20, 30, 4096));
String raw = """
{"found":true,"evidenceBlocks":[
{"source":"doc-1","title":"One","breadcrumb":"a","content":"123456789","score":0.99},
{"source":"doc-1","title":"Duplicate","content":"ignored"},
{"source":"doc-2","title":"Two","content":"second"}],
"contextPack":{"packedText":"secret internal context"},"retrievalTrace":{"attempts":[]},"rerankTrace":{}}
""";
ProjectedToolResult projected = projector.project(new RagToolRequest("timeout"), "framework-1", raw);
JsonNode json = objectMapper.readTree(projected.agentResult());
assertEquals(EvidenceStatus.EVIDENCE_FOUND, projected.evidenceStatus());
assertEquals("framework-1", json.path("tool_call_id").asText());
assertEquals(2, json.path("returned_count").asInt());
assertEquals("12345678", json.path("evidence").get(0).path("excerpt").asText());
assertTrue(json.path("truncated").asBoolean());
assertFalse(projected.agentResult().contains("contextPack"));
assertFalse(projected.agentResult().contains("retrievalTrace"));
assertFalse(projected.agentResult().contains("score"));
}
@Test
void returnsNoEvidenceForEmptyUsableBlocks() throws Exception {
RagResultProjector projector = new RagResultProjector(objectMapper, ToolProjectionLimits.defaults());
ProjectedToolResult projected = projector.project(new RagToolRequest("unknown"), "framework-2",
"{\"found\":false,\"evidenceBlocks\":[{\"source\":\"doc\",\"content\":\"\"}]}");
assertEquals(EvidenceStatus.NO_EVIDENCE, projected.evidenceStatus());
assertEquals(0, objectMapper.readTree(projected.agentResult()).path("evidence").size());
}
}