跳转至

PHP 独立消费者

在自己的应用中使用已发布的 axisvela/praxo-core 0.7.0,要求已修补的 PHP 版本 及 Composer 2。Windows 上使用 PHP 8.2.35-1、8.3.36-1、8.4.27-1、 8.5.12-1 或更新的已修补版本;这些版本包含 Brotli 1.2.1 与 CVE-2026-111497 修复。无需检出 PRAXO-CORE 源码、API Key、模型服务或 AxisVela 产品。 安装需要 Packagist 访问或已有 Composer 缓存。

这是扩展语言 starter,只演示 allow/deny 路径,不在四个具备治理负向矩阵的试点语言范围内。

选择边界

适用于需要显式工具范围和 Policy 的嵌入式模型/工具循环。应用仍负责模型访问、真实身份、 持久审批、外部效果和隔离。它不是托管 Agent 平台、持久图引擎或沙箱服务。 选型前阅读何时不应选择 Core。

创建应用

在 composer.json 中声明精确依赖:

{
  "require": {
    "axisvela/praxo-core": "0.7.0"
  }
}

然后运行 composer install。验收时不要使用 path 仓库、本地归档或源码检出中的 autoload.php。

应用源码

以下为 starter.php,来自受测源码。

<?php

declare(strict_types=1);

$autoload = __DIR__ . '/vendor/autoload.php';
if (is_file($autoload)) {
    require $autoload;
} else {
    require __DIR__ . '/../../language/php/src/autoload.php';
}

use Praxo\Core\Action;
use Praxo\Core\Agent;
use Praxo\Core\AgentOptions;
use Praxo\Core\Decision;
use Praxo\Core\FuncTool;
use Praxo\Core\Message;
use Praxo\Core\Model;
use Praxo\Core\PolicyHook;
use Praxo\Core\PraxoException;
use Praxo\Core\Request;
use Praxo\Core\Response;
use Praxo\Core\Roles;
use Praxo\Core\Session;
use Praxo\Core\ToolCall;
use Praxo\Core\ToolRegistry;

final class FixtureModel implements Model
{
    public function __construct(private readonly string $target)
    {
    }

    public function generate(Request $request): Response
    {
        $last = $request->messages[count($request->messages) - 1];
        if ($last->role === Roles::TOOL) {
            return new Response(Message::assistant('recorded'));
        }
        return new Response(Message::assistant('', [
            new ToolCall('record-1', 'record', ['target' => $this->target]),
        ]));
    }
}

final class StarterPolicy implements PolicyHook
{
    public function authorize(Action $action): Decision
    {
        return match ($action->kind) {
            'model.generate' => Decision::allow('model generation'),
            'tool.invoke' => $action->toolName === 'record'
                && ($action->arguments['target'] ?? null) === 'public'
                ? Decision::allow('public target')
                : Decision::deny('only the public target is permitted'),
            default => Decision::deny('unsupported action'),
        };
    }
}

final class Starter
{
    /** @return array{0: string, 1: int, 2: ?Throwable} */
    public static function runScenario(string $target): array
    {
        $calls = 0;
        $registry = new ToolRegistry();
        $registry->register(new FuncTool('record', 'Record a target in an in-memory counter.',
            static function (array $arguments) use (&$calls): array {
                $calls++;
                return ['output' => 'recorded'];
            }));

        $options = new AgentOptions(new FixtureModel($target));
        $options->tools = $registry;
        $options->allowedTools = ['record'];
        $options->policy = new StarterPolicy();

        try {
            $result = (new Agent($options))->run(new Session('consumer-' . $target), 'Record the requested target.');
            return [$result->output, $calls, null];
        } catch (PraxoException $error) {
            return ['', $calls, $error];
        }
    }

    public static function run(): void
    {
        [$output, $calls, $error] = self::runScenario('public');
        if ($error !== null || $output !== 'recorded' || $calls !== 1) {
            throw new RuntimeException("allow verification failed: output={$output} effects={$calls} error={$error?->getMessage()}");
        }
        echo "allow: recorded; effects=1\n";

        [, $calls, $error] = self::runScenario('private');
        if ($error === null || $error->praxoCode !== 'AGENT_POLICY_DENIED' || $calls !== 0) {
            throw new RuntimeException("deny verification failed: effects={$calls} error={$error?->getMessage()}");
        }
        echo "deny: policy rejected; effects=0\n";
    }
}

if (realpath($_SERVER['SCRIPT_FILENAME'] ?? '') === __FILE__) {
    Starter::run();
}

验收测试

以下为 test_starter.php,来自受测源码。

<?php

declare(strict_types=1);

require __DIR__ . '/starter.php';

[$output, $calls, $error] = Starter::runScenario('public');
if ($error !== null || $output !== 'recorded' || $calls !== 1) {
    throw new RuntimeException("allow: output={$output} effects={$calls} error={$error?->getMessage()}");
}

[, $calls, $error] = Starter::runScenario('private');
if ($error === null || $error->praxoCode !== 'AGENT_POLICY_DENIED' || $calls !== 0) {
    throw new RuntimeException("deny: effects={$calls} error={$error?->getMessage()}");
}

echo "php consumer tests passed\n";

验证

composer install
composer test

程序预期输出:

allow: recorded; effects=1
deny: policy rejected; effects=0

模型是确定性 fixture,不是真实 LLM。allowedTools 限制工具名,Policy 额外检查目标 参数。被拒绝的 private 目标抛出带 AGENT_POLICY_DENIED 的 PraxoException,且不得 增加内存计数器。替换为真实适配器后仍需保留这些测试。

若使用无 Composer 的源码检出,starter 会回退到 language/php/src/autoload.php; 该路径不算独立消费者证据。

运行时安全

PRAXO-CORE 不捆绑 PHP 或 Brotli。请使用已修补的 PHP 运行时。PHP Windows 构建 8.2.35-1、8.3.36-1、8.4.27-1、8.5.12-1 或更新的已修补版本包含 Brotli 1.2.1,修复 Brotli 解压越界写漏洞 CVE-2026-111497。Linux 发行版与容器可能提供等效 backport, 部署前请核对其安全公告状态。

范围

本 starter 仅证明指定包与 fixture 行为,不包含治理负向矩阵,也不证明真实身份、 持久审批、效果对账或隔离。后续参考 PHP 实现指南及 安全模型。