Java 独立消费者¶
在自己的应用中使用 Maven Central 已发布的 com.axisvela:praxo-core:0.9.0,
要求 Java 21 或更新版本、Maven 3.9 或更新版本。无需检出 PRAXO-CORE 源码、API Key、
模型服务或 AxisVela 产品。安装需要 Maven Central 访问或已有本地仓库缓存。
选择边界¶
适用于需要显式工具范围与 Policy 的嵌入式模型/工具循环。应用仍负责模型访问、真实身份、 持久审批、外部效果和隔离。它不是托管 Agent 平台、持久图引擎或沙箱服务。 选型前阅读何时不应选择 Core。
创建应用¶
在 pom.xml 中声明精确依赖:
<dependency>
<groupId>com.axisvela</groupId>
<artifactId>praxo-core</artifactId>
<version>0.9.0</version>
</dependency>
不要使用 system scope、本地 JAR 路径或框架源码替代构建。测试会验证 SDK 版本,
并确认 Version 来自已发布的 praxo-core-0.9.0.jar。
应用源码¶
以下为 src/main/java/starter/App.java,直接渲染自
受测源码。
package starter;
import com.axisvela.praxo.PraxoException;
import com.axisvela.praxo.agent.Agent;
import com.axisvela.praxo.agent.AgentOptions;
import com.axisvela.praxo.agent.Session;
import com.axisvela.praxo.model.Message;
import com.axisvela.praxo.model.Model;
import com.axisvela.praxo.model.Request;
import com.axisvela.praxo.model.Response;
import com.axisvela.praxo.model.ToolCall;
import com.axisvela.praxo.model.ToolSpec;
import com.axisvela.praxo.policy.Action;
import com.axisvela.praxo.policy.Decision;
import com.axisvela.praxo.tool.Registry;
import com.axisvela.praxo.tool.Tool;
import java.io.PrintStream;
import java.util.List;
import java.util.Map;
public final class App {
private App() {
}
static final class FixtureModel implements Model {
private final String target;
FixtureModel(String target) {
this.target = target;
}
@Override
public Response generate(Request request) {
Message last = request.messages().get(request.messages().size() - 1);
if (Message.TOOL.equals(last.role())) {
return Response.of(Message.assistant("recorded", List.of()));
}
ToolCall call = new ToolCall("record-1", "record", Map.of("target", target));
return Response.of(Message.assistant("", List.of(call)));
}
}
static final class RecordTool implements Tool {
private int calls;
@Override
public String name() {
return "record";
}
@Override
public String description() {
return "Record a target in an in-memory counter.";
}
@Override
public Object invoke(Map<String, Object> args) {
calls += 1;
return "recorded";
}
public ToolSpec spec() {
return new ToolSpec(
"record",
description(),
Map.of(
"type", "object",
"required", List.of("target"),
"properties", Map.of("target", Map.of("type", "string"))),
"write");
}
int calls() {
return calls;
}
}
public record Built(Agent agent, RecordTool tool) {
}
static Built buildAgent(String target) {
Registry tools = new Registry();
RecordTool tool = new RecordTool();
tools.register(tool);
AgentOptions options = new AgentOptions(new FixtureModel(target));
options.tools = tools;
options.maxSteps = 3;
options.allowedTools = List.of("record");
options.policy = (Action action) -> {
if ("model.generate".equals(action.kind())) {
return Decision.allow("model generation");
}
if ("tool.invoke".equals(action.kind())) {
Object value = action.arguments() == null
? null
: action.arguments().get("target");
return "record".equals(action.toolName()) && "public".equals(value)
? Decision.allow("public target")
: Decision.deny("only the public target is permitted");
}
return Decision.deny("unsupported action");
};
return new Built(new Agent(options), tool);
}
public static void run(PrintStream out) throws Exception {
Built allowed = buildAgent("public");
var result = allowed.agent().run(
new Session("starter-public"), "Record the requested target.");
if (!"recorded".equals(result.output) || allowed.tool().calls != 1) {
throw new IllegalStateException(
"allow verification failed: output=" + result.output
+ " effects=" + allowed.tool().calls);
}
out.println("allow: recorded; effects=1");
Built denied = buildAgent("private");
try {
denied.agent().run(
new Session("starter-private"), "Record the requested target.");
} catch (PraxoException error) {
if (!"AGENT_POLICY_DENIED".equals(error.code)) {
throw error;
}
if (denied.tool().calls != 0) {
throw new IllegalStateException(
"denied call produced effects=" + denied.tool().calls);
}
out.println("deny: policy rejected; effects=0");
return;
}
throw new IllegalStateException("private target was not denied");
}
public static void main(String[] args) throws Exception {
run(System.out);
}
}
验收测试¶
以下为 src/test/java/starter/AppTest.java,直接渲染自
受测源码。
package starter;
import com.axisvela.praxo.PraxoException;
import com.axisvela.praxo.Version;
import com.axisvela.praxo.agent.Session;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
class AppTest {
@Test
void allowedToolRunsOnce() throws Exception {
var built = App.buildAgent("public");
var result = built.agent().run(
new Session("test-public"), "Record the requested target.");
assertEquals("recorded", result.output);
assertEquals(1, built.tool().calls());
}
@Test
void deniedToolHasNoEffect() {
var built = App.buildAgent("private");
PraxoException error = assertThrows(PraxoException.class, () -> built.agent().run(
new Session("test-private"), "Record the requested target."));
assertEquals("AGENT_POLICY_DENIED", error.code);
assertEquals(0, built.tool().calls());
}
@Test
void starterOutputIsDeterministic() throws Exception {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try (PrintStream out = new PrintStream(buffer, true, StandardCharsets.UTF_8)) {
App.run(out);
}
String expected = "allow: recorded; effects=1" + System.lineSeparator()
+ "deny: policy rejected; effects=0" + System.lineSeparator();
assertEquals(expected, buffer.toString(StandardCharsets.UTF_8));
}
@Test
void dependencyComesFromPublishedJar() throws Exception {
assertEquals("0.9.0", Version.SDK_VERSION);
URL location = Version.class.getProtectionDomain().getCodeSource().getLocation();
String filename = Path.of(location.toURI()).getFileName().toString();
assertEquals("praxo-core-0.9.0.jar", filename);
assertTrue(location.toURI().toString().contains("/com/axisvela/praxo-core/0.9.0/"));
}
}
验证¶
mvn -B test
mvn -q -B exec:java
程序预期输出:
allow: recorded; effects=1
deny: policy rejected; effects=0
模型是确定性 fixture,不是真实 LLM。allowedTools 限制工具名,Policy 额外检查目标参数。
被拒绝的调用抛出 PraxoException,错误码为 AGENT_POLICY_DENIED,不得增加内存效果计数。
当前 API 的空允许列表不表示 deny-all。替换为真实模型适配器后仍需保留这些测试。
治理验收¶
GovernedAcceptanceTest 增加五项负向检查:
mvn -B -Dtest=GovernedAcceptanceTest test
| 检查 | 必须结果 |
|---|---|
| 缺少必需 Policy | 应用守卫在运行前拒绝;工具零副作用 |
| 工具范围为空 | 应用守卫拒绝;工具零副作用 |
| Middleware 改变最终参数 | 最终操作重新授权;拒绝阻止副作用 |
| 审批编辑改变参数 | 编辑后的操作重新授权;拒绝阻止副作用 |
| Policy 服务错误 | 运行 fail closed;工具零副作用 |
使用受测源码。 守卫属于应用层:Core 保留普通 nil-policy 行为。这些检查不证明真实身份、持久审批、 效果对账或通用安全 Profile。
这些检查仅证明指定发布产物与 fixture 行为,不证明真实模型质量、身份真实性、隔离、持久效果 或生产安全。后续参考Java 实现指南及安全模型。