调整项目结构

This commit is contained in:
2026-08-29 14:02:28 +08:00
parent c56aa6e752
commit c13302c0cb
52 changed files with 55270 additions and 124 deletions

View File

@@ -0,0 +1,27 @@
package tech.easyflow.manuagent.agent;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
/**
* 验证 Agent 事件持久化的精简规则。
*/
class AgentExecutionServiceTest {
/**
* 验证文档视觉结果保留图片路径元数据并丢弃 Base64 正文。
*/
@Test
void shouldStripInlineImageDataFromPersistedToolResult() {
String content = """
document_view_result={"images":[{"path":"work/tmp/document-view/a/render-1.png"}]}
{"type":"image","source":{"media_type":"image/png","data":"very-large-base64"}}
""";
String result = AgentExecutionService.stripInlineImageData(content);
assertThat(result).contains("render-1.png");
assertThat(result).doesNotContain("very-large-base64");
}
}

View File

@@ -0,0 +1,81 @@
package tech.easyflow.manuagent.agent;
import static org.assertj.core.api.Assertions.assertThat;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.agentscope.core.model.transport.HttpTransportException;
import io.agentscope.core.skill.AgentSkill;
import java.nio.file.Path;
import java.util.Map;
import org.junit.jupiter.api.Test;
/**
* 验证 Agent Run 的模型重连边界。
*/
class AgentRunServiceTest {
/**
* 网络故障和服务端错误允许重连,参数错误保持原始失败。
*/
@Test
void shouldRetryOnlyRecoverableModelFailures() {
assertThat(AgentExecutionService.MAX_MODEL_RECONNECTS).isEqualTo(5);
assertThat(AgentExecutionService.isRetryableModelFailure(
new RuntimeException(new HttpTransportException("disconnected")))).isTrue();
assertThat(AgentExecutionService.isRetryableModelFailure(
new HttpTransportException("unavailable", 503, ""))).isTrue();
assertThat(AgentExecutionService.isRetryableModelFailure(
new HttpTransportException("invalid request", 400, ""))).isFalse();
assertThat(AgentExecutionService.isRetryableModelFailure(
new IllegalArgumentException("invalid prompt"))).isFalse();
}
/**
* 验证上下文压缩只按模型窗口 90% Token 触发。
*/
@Test
void shouldCompactAtNinetyPercentTokensOnly() {
var config = AgentFactory.compactionFor(100_000, "summary");
assertThat(config.getTriggerTokens()).isEqualTo(90_000);
assertThat(config.getTriggerMessages()).isZero();
assertThat(config.getSummaryPrompt()).isEqualTo("summary");
}
/**
* 验证 Agent 调用 Skill 时使用无来源后缀的名称,并完整保留仓库信息。
*/
@Test
void shouldExposeCanonicalSkillIdWithoutLosingSkillInformation() {
AgentSkill source = new AgentSkill(
Map.of("name", "pdf", "description", "读取 PDF", "version", "1.0"),
"使用说明",
Map.of("references/guide.md", "参考内容"),
"imported",
Path.of("/skills/pdf"));
AgentSkill canonical = AgentFactory.canonicalSkill(source);
assertThat(canonical.getSkillId()).isEqualTo("pdf");
assertThat(canonical.getMetadata()).isEqualTo(source.getMetadata());
assertThat(canonical.getSkillContent()).isEqualTo(source.getSkillContent());
assertThat(canonical.getResources()).isEqualTo(source.getResources());
assertThat(canonical.getSource()).isEqualTo("imported");
assertThat(canonical.getOriginDir()).isEqualTo(source.getOriginDir());
}
/**
* 验证模型输出年份数组时可归一化为前端和确认接口使用的规划年数。
*/
@Test
void shouldNormalizePlanningYearArray() {
ObjectNode plan = new ObjectMapper().createObjectNode();
plan.putArray("planningYears").add("2026").add("2027");
AgentOutputService.normalizePlanningYears(plan);
assertThat(plan.path("planningYears").asInt()).isEqualTo(2);
assertThat(plan.path("planningPeriod").asText()).isEqualTo("2026-2027");
}
}

View File

@@ -0,0 +1,28 @@
package tech.easyflow.manuagent.agent;
import static org.assertj.core.api.Assertions.assertThat;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.agentscope.core.model.ToolSchema;
import io.agentscope.core.tool.Toolkit;
import org.junit.jupiter.api.Test;
/**
* 验证文档视觉工具可以被 AgentScope 注册并暴露结构化参数。
*/
class DocumentViewToolTest {
/**
* 验证多视图参数能够进入模型工具定义。
*/
@Test
void shouldRegisterMultiViewSchema() {
Toolkit toolkit = new Toolkit();
toolkit.registerTool(new DocumentViewTool(new ObjectMapper()));
ToolSchema schema = toolkit.getToolSchemas().getFirst();
assertThat(schema.getName()).isEqualTo("document_view");
assertThat(schema.getParameters().toString()).contains("views", "path", "page", "sheet", "range");
}
}

View File

@@ -0,0 +1,66 @@
package tech.easyflow.manuagent.agent;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.agentscope.core.agent.RuntimeContext;
import io.agentscope.harness.agent.filesystem.model.ExecuteResponse;
import io.agentscope.harness.agent.filesystem.sandbox.AbstractSandboxFilesystem;
import io.agentscope.harness.agent.workspace.WorkspacePathNormalizer;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import org.junit.jupiter.api.Test;
/**
* 验证文件读取工具不会静默丢失后续内容。
*/
class PagedReadFileToolTest {
/**
* 沙箱返回分页元数据时,应将下一页位置明确告知 Agent。
*/
@Test
void shouldTellAgentHowToContinueReading() {
AbstractSandboxFilesystem filesystem = mock(AbstractSandboxFilesystem.class);
String content = Base64.getEncoder().encodeToString("第一行\n第二行".getBytes(StandardCharsets.UTF_8));
when(filesystem.execute(any(RuntimeContext.class), anyString(), isNull()))
.thenReturn(new ExecuteResponse(
"{\"ok\":true,\"truncated\":true,\"nextOffset\":2,\"returnedLines\":2,\"lineTooLong\":false}\n"
+ content,
0,
false));
PagedReadFileTool tool = new PagedReadFileTool(
filesystem, WorkspacePathNormalizer.of("/workspace"), new ObjectMapper());
String result = tool.readFile(RuntimeContext.empty(), "/workspace/work/report.txt", 0, 2);
assertThat(result).contains("第一行\n第二行");
assertThat(result).contains("内容未读完");
assertThat(result).contains("offset=2");
}
/**
* 沙箱自身发生截断时,应明确提示重试,不能把残缺内容当作完整结果。
*/
@Test
void shouldExposeUnexpectedSandboxTruncation() {
AbstractSandboxFilesystem filesystem = mock(AbstractSandboxFilesystem.class);
when(filesystem.execute(any(RuntimeContext.class), anyString(), isNull()))
.thenReturn(new ExecuteResponse(
"{\"ok\":true,\"truncated\":true,\"nextOffset\":10,\"returnedLines\":10,\"lineTooLong\":false}\n",
0,
true));
PagedReadFileTool tool = new PagedReadFileTool(
filesystem, WorkspacePathNormalizer.of("/workspace"), new ObjectMapper());
String result = tool.readFile(RuntimeContext.empty(), "work/report.txt", 0, 10);
assertThat(result).contains("内容未读完");
assertThat(result).contains("offset=10");
}
}