PHP code example of likun-mci / php-ai

1. Go to this page and download the library: Download likun-mci/php-ai library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

likun-mci / php-ai example snippets




use Ai\AI;



use Ai\AI;

use Ai\AI;

$ai = AI::create([
    'model'   => 'gpt-4o',
    'api_key' => 'sk-xxxxxxxxxxxxx',
]);

$response = $ai->chat('用一句话介绍人工智能');

echo $response->getContent();          // 回复文本
echo $response->tokens();              // 总消耗 tokens

$ai = AI::create(['protocol' => 'qwen',     'model' => 'qwen-plus',  'api_key' => 'sk-xxx']);  // 通义千问
$ai = AI::create(['protocol' => 'zhipu',    'model' => 'glm-4.6',    'api_key' => 'xxx']);     // 智谱 GLM
$ai = AI::create(['protocol' => 'doubao',   'model' => 'doubao-seed-1-6', 'api_key' => 'xxx']);// 豆包
$ai = AI::create(['protocol' => 'moonshot', 'model' => 'kimi-latest','api_key' => 'sk-xxx']);  // Kimi

// 模型名能识别出平台时,protocol 也可以省略
$ai = AI::create(['model' => 'qwen-plus', 'api_key' => 'sk-xxx']);

$response = $ai->chat([
    'messages' => [
        ['role' => 'system',    'content' => '你是一个有帮助的助手'],
        ['role' => 'user',      'content' => '介绍一下人工智能'],
        ['role' => 'assistant', 'content' => '人工智能是……'],
        ['role' => 'user',      'content' => '再简短一点'],
    ],
    'temperature' => 0.7,
    'max_tokens'  => 1000,
]);

// 换平台只改这两行,业务代码不动
$ai = AI::create([
    'protocol' => 'qwen',          // 见下表
    'model'    => 'qwen-plus',
    'api_key'  => 'sk-xxx',
]);
echo $ai->chat('你好')->getContent();

// 官方新模型:识别出 claude 家族,自动使用 api.anthropic.com/v1/messages
$ai = AI::create(['model' => 'claude-opus-5', 'api_key' => 'sk-ant-xxx']);

// 国产新模型:识别出 zhipu 家族,自动使用 open.bigmodel.cn/api/paas/v4/chat/completions
$ai = AI::create(['model' => 'glm-4.6', 'api_key' => 'xxx']);

// 开源模型 / 第三方接口:任意模型名 + 手选协议 + 自定义地址
$ai = AI::create([
    'model'    => 'llama3',
    'protocol' => 'openai',                    // 手选协议格式
    'base_url' => 'http://10.0.0.9:11434/v1',  // 自定义接口地址
]);

// 用 GLM-4.6 跑工具调用:协议是 Claude 的,价格与密钥是智谱的
$ai = AI::create([
    'protocol' => 'zhipu-anthropic',
    'model'    => 'glm-4.6',
    'api_key'  => $config['zhipu__api_key'],
]);

$ai = new AI();

// —— 不发请求的元信息查询(适合渲染后台下拉框) ——
$ai->listPlatforms();        // 37 个平台:['deepseek'=>'DeepSeek 深度求索','qwen'=>'阿里云百炼(通义千问)',...]
                             // 键即业务层约定的密钥前缀 {平台}__api_key
$ai->listProtocols();        // 40 个协议:['openai'=>'OpenAI 兼容(Chat Completions)','qwen'=>'阿里云百炼 / 通义千问(OpenAI 兼容)',...]
$ai->listProtocolGroups();   // 同上,但按「中国大陆 / 海外主流 / 聚合中转 / 本地部署」分组,可直接渲染 optgroup
$ai->listKnownModels('qwen');// 该平台的常用模型:['qwen3-max'=>'通义千问 3 Max','qwen-max'=>'通义千问 Max',...]
$ai->platformOfModel('qwen-max');   // 'qwen'(设置模型前即可安全调用,无法归属官方平台时返回 custom)

// —— 设置模型后的当前状态 ——
$ai->setConfig(['model' => 'glm-4.6', 'api_key' => 'xxx']);
$ai->getPlatform();     // 'zhipu'
$ai->getProtocolKey();  // 'zhipu',当前实际使用的协议
$ai->resolveEndpoint(); // 'https://open.bigmodel.cn/api/paas/v4/chat/completions',当前实际请求端点
$ai->listKnownModels(); // 不传参数时取当前协议的内置清单

// —— 实时调用平台接口拉取模型 ——
$ai->listModels();      // 端点跟随 base_url / endpoint 走,接第三方网关时列的就是网关的模型
                        // 拉取失败或平台无此接口时:若请求的是官方域名,回退到内置常用清单;
                        // 接的是第三方网关则返回 null(避免把官方清单误当成网关的能力)
$ai->listModels(true);  // 传入 true 返回完整模型数据(含 id / created / owned_by / pricing 等)
                        // 适用于 OpenRouter、硅基流动等需要展示模型价格/能力标签的场景

$platforms = $this->ai->listPlatforms();
$result    = [];

foreach ($platforms as $platform => $platformName) {
    $apiKey = (string)($this->siteConfig["{$platform}__api_key"] ?? '');
    if (empty($apiKey)) continue;               // 未配置 Key 的平台直接跳过

    try {
        $ai = new AI();
        // 该平台任一模型即可(用于确定协议),直接取库内置清单的第一个
        $known = $ai->listKnownModels(\Ai\Helpers\Protocols::platformProtocols($platform)[0] ?? '');
        $ai->setConfig([
            'model'   => (string)array_key_first($known),
            'api_key' => $apiKey,
        ]);
        $models = $ai->listModels();
        if (is_array($models) && $models) $result[$platform] = $models;
    } catch (\Exception $e) {
        // 单平台失败不影响其它平台
    }
}

$ai->setConfig([
    'model'        => 'gpt-4o',      // 模型标识(必填),内置标识或任意自定义模型名
    'api_key'      => 'sk-xxx',      // API 密钥(自建/内网接口可不填)
    'protocol'     => '',            // 手选协议格式:见「平台一览」,或自定义协议类名
    'tools'        => [],            // 工具定义(统一格式,见「Agent:工具调用循环」)
    'tool_choice'  => null,          // auto / any / none / ['type'=>'tool','name'=>'x']
    'base_url'     => '',            // 接口根地址,与协议官方路径智能拼接,见「自定义接口地址」
    'endpoint'     => '',            // 完整对话端点,原样使用,优先级高于 base_url
    'endpoint_models' => '',         // 完整模型列表端点(仅 listModels 生效)
    'platform'     => '',            // 平台名,仅供业务层标识,默认由模型名/协议决定
    'headers'      => [],            // 追加/覆盖请求头,值为 null 表示删除协议默认头
    'extra_body'   => [],            // 追加到请求体的私有参数
    'search'       => false,         // 联网搜索,true 或细化数组,见「联网搜索」
    'max_tokens'             => 1024 * 64,  // 最大输出 tokens
    'max_completion_tokens'  => 16384,      // 仅 OpenAI o1/o3 系列,覆盖 max_tokens
    'temperature'            => 0.7,        // 温度
    'organization' => 'org-xxx',     // 仅 OpenAI 企业账号
    'project_id'   => 'proj_xxx',    // 仅 OpenAI 企业账号
]);

$ai->setConfig([
    'headers'    => [
        'Authorization' => null,      // 删掉协议默认写入的 Bearer 头
        'X-Api-Token'   => 'abc123',  // 换成对方要求的鉴权头
    ],
    'extra_body' => ['enable_thinking' => false, 'safe_mode' => 1],
]);

$ai->setModel('claude-3-opus')   // 单独切换模型
   ->setTimeout(300)             // 超时(秒),长文本生成务必调大
   ->setConnectTimeout(30)       // 连接超时(秒),独立于总超时
   ->setUserAgent('MyApp/1.0')   // 自定义 User-Agent(默认不发送)
   ->setSslVerify(false)         // 禁用 SSL 校验(仅调试/内网自签)
   ->setProxy('socks5h://127.0.0.1:1080')
   ->setStream(true)
   ->setStreamCallback($fn)      // 流式分片交给回调(常驻内存框架必用,见「流式输出」章节)
   ->setAttachments([$file])
   ->chat($prompt);

if (!empty($config['PROXY_SOCKS5'])) {
    $ai->setProxy($config['PROXY_SOCKS5']);
} elseif (!empty($config['PROXY_HTTP'])) {
    $ai->setProxy($config['PROXY_HTTP']);
}

$ai = AI::create([
    'model'    => 'deepseek-chat',
    'api_key'  => 'sk-xxx',
    'base_url' => 'https://proxy.example.com',   // 或带端口 http://127.0.0.1:8080
]);
// 实际请求 => https://proxy.example.com/v1/chat/completions

$ai = AI::create([
    'model'    => 'deepseek-chat',
    'api_key'  => 'sk-xxx',
    'endpoint' => 'https://proxy.example.com/openai/deepseek/chat',  // 原样使用
]);

// 方式一(推荐):手选 protocol = openrouter,自动使用 openrouter.ai/api
$ai = AI::create([
    'model'    => 'openai/gpt-4o',              // OpenRouter 上的完整模型标识
    'protocol' => 'openrouter',
    'api_key'  => 'sk-or-v1-xxxxxxxxx',         // OpenRouter API Key
    'referer'  => 'https://myapp.com',          // 可选,来源标识(OpenRouter 后台查看)
    'title'    => 'MyApp',                      // 可选,应用名称
]);

// 方式二:用 base_url 配置(不依赖 protocol=openrouter)
$ai = AI::create([
    'model'    => 'anthropic/claude-sonnet-4-20250514',
    'base_url' => 'https://openrouter.ai/api',
    'api_key'  => 'sk-or-v1-xxxxxxxxx',
]);

$ai = AI::create([
    'protocol' => 'openrouter',
    'api_key'  => 'sk-or-v1-xxx',
]);
$models = $ai->setModel('openai/gpt-4o')->listModels(true);
// 返回含 id、pricing、context_length 等的完整模型信息

// API2D(国内 OpenAI 中转)
AI::create(['model'=>'gpt-4o', 'protocol'=>'openai', 'api_key'=>'fkxxxxx',
            'base_url'=>'https://openapi.api2d.com']);

// Cloudflare AI Gateway(官方聚合网关)
AI::create(['model'=>'@cf/meta/llama-3-8b-instruct', 'protocol'=>'openai', 'api_key'=>'xxx',
            'base_url'=>'https://gateway.ai.cloudflare.com/v1/{account_id}/{gateway_id}');

// one-api / new-api(自建聚合网关,最通用)
AI::create(['model'=>'glm-4.6', 'protocol'=>'openai', 'api_key'=>'sk-xxx',
            'base_url'=>'https://gateway.example.com']);

// 自建 Anthropic 兼容网关(Agent 工具调用需要 claude 协议)
AI::create(['model'=>'my-agent-model', 'protocol'=>'anthropic', 'api_key'=>'k',
            'base_url'=>'http://127.0.0.1:8080/gw']);
// => http://127.0.0.1:8080/gw/v1/messages

// 内网自建服务(无需 Key,用私有鉴权头)
AI::create(['model'=>'llama3', 'protocol'=>'openai',
            'base_url'=>'http://10.0.0.9:11434/v1',
            'headers' =>['Authorization'=>null, 'X-Internal-Token'=>'t']]);

// 内置协议 + 自定义地址:走自己的代理,但仍按该平台的协议与鉴权方式通信
AI::create(['model'=>'qwen-plus', 'protocol'=>'qwen', 'api_key'=>'sk-xxx',
            'base_url'=>'https://my-proxy.example.com/dashscope']);

echo $ai->resolveEndpoint();   // 返回按配置解析后的实际端点

use Ai\Cli\ClaudeCode;

$cli = ClaudeCode::create([
    'workdir' => '/var/www',   // 工作目录(AI 的文件操作都发生在这里)
]);

$response = $cli->chat('检查一下 index.php 的语法,有问题直接改');
echo $response->getContent();   // 最终文本
echo $response->getSessionId(); // 会话 ID
echo $response->getCostUsd();   // CLI 实测费用(USD)

$cli = ClaudeCode::create();            // 自动检测(含缓存)
$cli->setBinary('/usr/local/bin/claude'); // 手动指定(优先级最高)

// 缓存控制
$cli->setBinaryCacheEnabled(true);      // 是否启用文件缓存(默认 true)
$cli->setBinaryCacheTtl(86400);         // 缓存有效期秒(默认 1 天)
$cli->clearBinaryCache();               // 清除缓存,强制重新探测

$cli
    ->setFlag('allowedTools', 'Read Grep Glob')   // 收紧工具权限
    ->setFlag('model', 'claude-sonnet-4-5')        // 指定模型
    ->setFlag('max-turns', 3)                      // 限制轮数
    ->setFlag('              // 删除默认参数
    ->resetFlags();                                // 恢复默认

$cli
    // 权限与工具
    ->setPermissionMode('acceptEdits')      // acceptEdits/auto/bypassPermissions/manual/dontAsk/plan
    ->setAllowedTools(['Read', 'Bash(git *)'])  // 免提示白名单,支持细粒度写法
    ->setDisallowedTools(['Bash'])          // 硬性拒绝(工具从工具集移除)
    ->setTools(['Read', 'Grep', 'Glob'])    // 限定可用工具集,传 [] 禁用全部工具
    ->setAddDirs(['/data/shared'])          // 工作目录之外还允许访问的目录
    ->setSkipPermissions(false)             // --dangerously-skip-permissions

    // 模型与推理
    ->setModel('claude-sonnet-5')
    ->setFallbackModel(['sonnet', 'haiku']) // 主模型过载时按序降级
    ->setEffort('high')                     // low/medium/high/xhigh/max
    ->setThinkingTokens(31999)              // 思考预算(IDE 插件用的就是这个值)

    // 成本与产出
    ->setMaxBudgetUsd(0.5)                  // 超预算即终止,无人值守强烈建议设置
    ->setJsonSchema(['type' => 'object', 'properties' => [...]])  // 结构化输出

    // 提示词与扩展
    ->setSystemPrompt('你是代码审查专家')
    ->appendSystemPrompt('输出务必用中文')
    ->setAgent('reviewer')
    ->setAgents(['reviewer' => ['description' => '代码审查', 'prompt' => '...']])
    ->setMcpConfig(['mcpServers' => ['fs' => ['command' => 'npx']]])
    ->setStrictMcpConfig()

    // 会话与配置源
    ->setSettingSources(['user', 'project'])   // 传 [] 表示不加载任何设置文件
    ->setSettings('/path/settings.json')
    ->setFixedSessionId('550e8400-e29b-41d4-a716-446655440000')  // 指定新会话 ID
    ->setForkSession()                      // 续接时分叉,不污染原会话
    ->setContinueLast()                     // 续接当前目录最近一次会话
    ->setSessionPersistence(false)          // 会话不落盘

    // 输出与诊断
    ->setPartialMessages()                  // token 级增量事件
    ->setIncludeHookEvents()
    ->setForwardSubagentText()
    ->setDebug('api,hooks')                 // --debug + --debug-to-stderr,日志走 stderr 事件
    ->setBare()                             // 精简模式,跳过 hooks/LSP/CLAUDE.md 自动发现
    ->setSafeMode();                        // 禁用全部自定义配置,排查用

$cli->setJsonSchema([
    'type'       => 'object',
    'properties' => [
        'severity' => ['type' => 'string'],
        'issues'   => ['type' => 'array', 'items' => ['type' => 'string']],
    ],
    '

$cli->runStream('帮我重构这段代码', function ($event, $data) {
    switch ($event) {
        case 'start':          break;  // ['resume' => bool]
        case 'init':           break;  // 会话初始信息:cwd、session_id、可用工具、MCP 服务器
        case 'text':           break;  // 助手正文文本块(string)
        case 'thinking':       break;  // 助手思考内容(string)
        case 'tool_use':       break;  // ['id','name','input']
        case 'tool_result':    break;  // ['tool_use_id','content','is_error']
        case 'text_delta':     break;  // token 级正文增量(需 setPartialMessages())
        case 'thinking_delta': break;  // token 级思考增量(需 setPartialMessages())
        case 'rate_limit':     break;  // 限流状态
        case 'system':         break;  // 其它 system 子类型(thinking_tokens、compact_boundary…)
        case 'error':          break;  // 本轮标记 is_error
        case 'message':        break;  // 原始 stream-json 事件(所有事件都会先经过这里)
        case 'stderr':         break;  // 排查日志(开 setDebug() 后调试输出走这里)
        case 'result':         break;  // 最终汇总
        case 'done':           break;
    }
});

$res = $cli->chat('第一问');
$cli->setSessionId($res->getSessionId());   // 下一轮自动 --resume 续接

$cli->chat('第二问', ['reset' => true]);    // 强制开启新会话

$cli->setRunner(function ($cmd, $onChunk) {
    $stream = ssh2_exec($conn, $cmd);         // 在宿主机执行同一命令
    $err = ssh2_fetch_stream($stream, SSH2_STREAM_STDERR);
    while ($buf = fread($stream, 8192))       { $onChunk($buf, 'out'); }
    while ($buf = fread($err, 8192))          { $onChunk($buf, 'err'); }
    return 0;
});

$cli->setSleeper(function ($sec) { \Swoole\Coroutine::sleep($sec); });

$cli = ClaudeCode::create();

// —— 子命令类查询(毫秒级)——
$cli->getVersion();       // '2.1.222'(实例内缓存)
$cli->isLoggedIn();       // true
$cli->getAuthStatus();    // ['loggedIn'=>true,'authMethod'=>'claude.ai','apiProvider'=>'firstParty',
                          //  'email'=>'...','orgId'=>'...','orgName'=>'...','subscriptionType'=>'max']
$cli->doctor();           // 安装体检报告(原始文本)
$cli->runCommand(['mcp', 'list']);   // 任意子命令:['exit_code'=>0,'stdout'=>'...','stderr'=>'']

// —— 控制协议类查询(秒级,内部临时起一个 claude 进程问完即关)——
$cli->listModels();       // ['default','opus[1m]','claude-fable-5[1m]','sonnet','haiku','opus']
                          // 返回值可直接传给 setModel()
$cli->listModels(true);   // 完整条目,含 resolvedModel / displayName / description /
                          // supportsEffort / supportedEffortLevels / supportsFastMode 等
$cli->getUsage();         // 用量与限流全量数据
$cli->getRateLimits();    // 精简后的额度概览(见下)
$cli->getSettings();      // 合并 user/project/local 后实际生效的设置
$cli->getMcpServers();    // MCP 服务器状态列表
$cli->getBinaryVersion(); // ['version'=>'2.1.222','buildTime'=>'2026-08-04T01:24:05Z']

foreach ($cli->getRateLimits() as $limit) {
    printf("%-14s 已用 %5.1f%%  %s后重置\n",
        $limit['key'],                        // session / weekly_all / weekly_scoped
        $limit['percent'],                    // 已用百分比
        gmdate('H:i:s', $limit['resets_in'])  // 距重置剩余秒数
    );
}
// session        已用  16.0%  01:49:47后重置
// weekly_all     已用  17.0%  06:39:47后重置
// weekly_scoped  已用   0.0%  06:39:47后重置

$s = ClaudeCodeSession::create(['workdir' => '/var/www']);
$s->send('第一问');
$s->send('第二问');

$usage = $s->getUsage();
echo $usage['session']['total_cost_usd'];   // 两轮累计花费
echo $s->getSessionCost();
// Total cost:            $0.0067
// Total duration (API):  36s
// Total code changes:    0 lines added, 0 lines removed
// Usage by model:
//     claude-haiku-4-5:  10 input, 52 output, 14.1k cache read, 2.5k cache write ($0.0067)

use Ai\Cli\ClaudeCodeSession;

$s = ClaudeCodeSession::create([
    'workdir'      => '/var/www',
    'turn_timeout' => 300,        // 单轮等待上限(秒)
]);

// 工具权限实时决策,等价于 IDE 里弹出的"是否允许执行"
$s->onPermission(function (array $req) {
    // $req: tool_name / display_name / input / description / tool_use_id / permission_suggestions
    if ($req['tool_name'] === 'Bash')  return '本环境禁止执行 shell 命令';   // 字符串 = 拒绝并说明理由
    if ($req['tool_name'] === 'Write' && strpos($req['input']['file_path'], '/etc/') === 0) {
        return false;                                                       // false = 拒绝
    }
    return true;                                                            // true = 放行
    // 也可返回 ['behavior' => 'allow', 'updatedInput' => [...]] 放行并改写入参
});

$a = $s->send('看一下 src 目录结构');
$b = $s->send('把刚才第一个文件的注释补全');   // 同一进程,上下文常驻,无需 --resume 重放
$s->close();

$s = ClaudeCodeSession::create(['workdir' => '/var/www'])
        ->setSleeper(function ($sec) { \Swoole\Coroutine::sleep($sec); });   // 协程环境必设

$s->post('把 src 下的注释补全');           // 立即返回,不等本轮结束

while ($clientAlive()) {
    while ($msg = $queue->pop()) {
        $s->post($msg);                     // 轮内轮外一样调:轮内 = 插入当前轮
    }

    $active = $s->tick($onEvent);           // 泵一批事件后立即返回

    if (!$active && ($res = $s->takeResult())) {
        $saveAnswer($res);                  // 本轮收口,进程留着等下一轮
    }
    $s->isTurnActive() ? $pause(0.02) : $pause(0.1);
}
$s->close();

$fired = false;
$res = $s->send('全量重构整个项目', function ($ev, $d) use ($s, &$fired) {
    if ($ev === 'tool_use' && !$fired) {
        $fired = true;
        $s->interrupt();          // 相当于 IDE 里的"停止"按钮,进程保活可继续下一轮
    }
});
echo $res->getSubtype();          // error_during_execution

$s->setPermissionMode('plan');    // 已启动时热切换(未启动则只改启动参数)
$s->switchModel('claude-haiku-4-5-20251001');
$s->switchThinkingTokens(31999);
$s->control(['subtype' => 'set_cwd', 'cwd' => '/srv/app']);   // 发送任意 control_request

$ai = AI::create(['model' => 'deepseek-chat', 'api_key' => 'sk-xxx']);

$response = $ai->setStream(true)->chat('写一篇关于人工智能的文章');

// 流式结束后仍可拿到完整内容与 tokens
$full = $response->getContent();
$used = $response->tokens();

$ai = new \Ai\AI();

if (!empty($siteConfig['PROXY_SOCKS5'])) $ai->setProxy($siteConfig['PROXY_SOCKS5']);

try {
    $ai->setStream(true)
       ->setConfig(['model' => $model, 'api_key' => $apiKey])
       ->setAttachments($attachments)
       ->chat($message);
} catch (\Ai\Exceptions\AIException $e) {
    // 流式已开始输出时,异常信息也只能顺着流写出去
    echo "data: " . json_encode(['type' => 'error', 'message' => $e->getMessage()]) . "\n\n";
}

$ai->setStream(true)->setStreamCallback(function($event) use ($response) {
    if ($event['type'] === 'stream_chunk' && $event['content'] !== null) {
        // $response 是 Swoole\Http\Response,write() 分块下发
        $response->write('data: ' . json_encode(['content' => $event['content']]) . "\n\n");
    }
    if ($event['type'] === 'stream_end') {
        $response->write('data: ' . json_encode($event['data']) . "\n\n");
    }
});

$full = $ai->chat($messages)->getContent();   // 返回值不受影响,仍是拼好的完整文本

$resp = $ai->setStream(true)->chat(['messages' => $msgs, 'tools' => $toolDefs]);

$resp->getStopReason();   // end_turn / max_tokens / tool_use …(可据此判断是否被截断)
$resp->getToolCalls();    // 分片已自动重组,格式与非流式完全一致

use Ai\Helpers\AIFile;

$image = AIFile::fromPath('/path/to/image.jpg');            // 本地文件,自动识别 MIME
$image = AIFile::fromPath($tmpName, $_FILES['x']['type']);  // 指定 MIME
$image = AIFile::fromUrl('https://example.com/image.jpg');  // 远程 URL

$response = $ai->setAttachments([$image])->chat('描述这张图片');

$ai = AI::create([
    'protocol' => 'qwen',
    'model'    => 'qwen-plus',
    'api_key'  => 'sk-xxx',
    'rounds'   => 5,          // 保留最近 5 轮上下文;默认 0 = 不启用
]);

echo $ai->chat('我叫小明')->getContent();
echo $ai->chat('我叫什么名字?')->getContent();   // 模型能答出"小明"

$ai->setSessionId('user-1001')->chat('我喜欢喝咖啡');
$ai->setSessionId('user-2002')->chat('我喜欢喝茶');      // 与上一个会话互不干扰

$ai->setSessionId('user-1001')->chat('我喜欢喝什么?');  // 答"咖啡"

// 请求结束时存起来
$redis->set("ai:history:{$uid}", json_encode($ai->exportHistory()));

// 下次请求恢复
$ai->importHistory(json_decode($redis->get("ai:history:{$uid}"), true) ?: []);

$messages = [];
$messages[] = ['role' => 'user', 'content' => '我叫小明'];
$resp = $ai->chat(['messages' => $messages]);

$messages[] = ['role' => 'assistant', 'content' => $resp->getContent()];
$messages[] = ['role' => 'user', 'content' => '我叫什么名字?'];
$resp = $ai->chat(['messages' => $messages]);

$ai->onBefore(function (&$payload) {
    // 请求前:可审计、可改写 payload
    log_message('debug', json_encode($payload));
    $payload['temperature'] = 0.5;
});

$ai->onAfter(function ($response) {          // onResponse() 是它的别名
    // 请求后:统计 tokens、写入用量表
    log_usage($response->getUsage());
});

use Ai\Exceptions\AIException;
use Ai\Exceptions\ConfigException;
use Ai\Exceptions\RequestException;

try {
    $response = $ai->chat($prompt);
} catch (ConfigException $e) {
    // 配置错误:模型不存在、未设置模型、协议未初始化
} catch (AIException $e) {
    // 请求失败:网络、鉴权、限流、平台报错,均在 chat() 内被包装成 AIException
    echo $e->getMessage();
    echo $e->getPlatform();      // 出错平台,如 'openai'
    echo $e->getErrorCode();     // 平台错误码
    print_r($e->getRawResponse()); // 平台原始错误响应,排查问题的关键
}

$ai->setTimeout(120);         // 默认 120 秒(推理模型单次经常跑一两分钟,60 秒会误杀)
$ai->setRetry(2);             // 默认重试 2 次;传 0 关闭
$ai->setRetry(3, 800, 30000); // 重试 3 次、退避基数 800ms、单次等待上限 30 秒

$ai->setTransport(new MyTransport());   // 实现 Ai\Contracts\TransportInterface 即可

$results = $ai->chatBatch([
    'title' => '把这句翻译成英文:你好',
    'intro' => '把这句翻译成英文:世界',
    'body'  => ['messages' => [['role' => 'user', 'content' => '翻译:再见']]],
], 5);   // 第二个参数是并发度,默认 5

foreach ($results as $key => $r) {
    if ($r->isSuccess()) {
        echo $key, ': ', $r->getContent(), "\n";
    } else {
        echo $key, ' 失败: ', $r->getError(), "\n";   // 单条失败不影响其它条
    }
}

use Ai\Helpers\Log;

Log::setLogger($monolog);                  // 任何 PSR-3 风格对象(不引入 psr/log 依赖)
Log::setLogger(function ($level, $message, array $context) {
    log_message($level, '[AI] ' . $message . ' ' . json_encode($context));
});
Log::setLogger(null);                      // 恢复默认的 error_log

$agent = (new Agent($ai))->setTools($tools)->onEvent($handler);
$agent->run([['role' => 'user', 'content' => '北京天气怎么样']]);

// 上面这段代码,$ai 换成下面任意一个都不用改:
AI::create(['protocol' => 'qwen',     'model' => 'qwen-plus',       'api_key' => '...']);
AI::create(['protocol' => 'zhipu',    'model' => 'glm-4.6',         'api_key' => '...']);
AI::create(['protocol' => 'doubao',   'model' => 'doubao-seed-1-6', 'api_key' => '...']);
AI::create(['protocol' => 'openai',   'model' => 'gpt-4o',          'api_key' => '...']);
AI::create(['protocol' => 'claude',   'model' => 'claude-opus-5',   'api_key' => '...']);

$ai->setStreamCallback(function ($event) use ($response) {
    if ($event['type'] === 'stream_chunk' && $event['content'] !== null) {
        $response->write('data: ' . json_encode(['content' => $event['content']]) . "\n\n");
    }
});

(new Agent($ai))
    ->setStream(true)          // 默认关闭,与旧版本一致
    ->setTools($tools)
    ->run([['role' => 'user', 'content' => '北京天气怎么样']]);

$agent = (new Agent($ai))->setTools($tools);

echo $agent->chat('我叫小明')->getText();
echo $agent->chat('我叫什么?')->getText();      // 记得住;中途的工具往返也留在上下文里

$agent->chat('把 /data 下的临时文件删了');

if ($agent->isAwaitingPermission()) {
    $agent->chat('先别删,改成移到 /backup');    // 不必 approve/deny,直接改口
}

$resp = $ai->chat(['messages' => $messages, 'tools' => $toolDefs]);

if ($resp->hasToolCalls()) {                      // 各平台一致
    $messages[] = $resp->toAssistantMessage();    // 把模型这一轮接回上下文

    $results = [];
    foreach ($resp->getToolCalls() as $call) {    // ['id'=>.., 'name'=>.., 'input'=>[..]]
        $results[] = [
            'type'        => 'tool_result',
            'tool_use_id' => $call['id'],
            'content'     => myHandler($call['name'], $call['input']),
        ];
    }
    $messages[] = ['role' => 'user', 'content' => $results];

    $resp = $ai->chat(['messages' => $messages, 'tools' => $toolDefs]);
}

echo $resp->getContent();
echo $resp->getStopReason();   // end_turn / tool_use / max_tokens / content_filter / refusal

$tools = [
    'sql_query' => [
        'description'  => '执行只读 SQL 查询。仅支持 SELECT/SHOW/DESCRIBE/EXPLAIN,最多返回 200 行。',
        'input_schema' => [
            'type'       => 'object',
            'properties' => [
                'sql' => ['type' => 'string', 'description' => '要执行的 SQL'],
            ],
            '

$ai = new \Ai\AI();
$ai->setConfig([
    'model'      => 'deepseek-anthropic',
    'api_key'    => $apiKey,
    'max_tokens' => 1024 * 64,
]);

$emit = function ($ev) {
    // 把 Agent 过程实时推给前端
    echo "data: " . json_encode($ev, JSON_UNESCAPED_UNICODE) . "\n\n";
    flush();
};

$agent = (new \Ai\Agent\Agent($ai))
    ->setSystem($systemPrompt)
    ->setTools($tools)
    ->setMaxIter(128)      // 需要逐页分析的任务要给足迭代次数,默认 25
    ->onEvent($emit);

$agent->run([['role' => 'user', 'content' => $message]]);

$reply = $agent->lastText();   // 最终自然语言回复

// 兼容旧版:直接取最终文本
$agent->run([['role' => 'user', 'content' => '北京天气']]);
echo $agent->lastText();

// 新版:通过 getRuntime() 获取结构化结果
$result = $agent->getRuntime()->run($messages);

echo $result->getText();           // 模型最终回答
echo $result->getStopReason();     // end_turn / max_iter / no_progress / ...
echo $result->getIterations();     // 迭代次数
print_r($result->getUsage());      // token 用量

if ($result->isDone()) {
    echo '正常结束';
} elseif ($result->isError()) {
    echo '异常停止:' . $result->getStopReason();
}

use Ai\Agent\Tool\AgentToolInterface;
use Ai\Agent\Tool\ToolContext;
use Ai\Agent\Tool\ToolResult;

class ReadFileTool implements AgentToolInterface
{
    public function name()        { return 'read_file'; }
    public function description() { return '读取工作区内的文件内容'; }
    public function schema()      { return ['type' => 'object', 'properties' => ['path' => ['type' => 'string']]]; }
    public function execute(array $input, ToolContext $context): ToolResult
    {
        return ToolResult::success('文件内容');
    }
}

$agent->setTools([new ReadFileTool()]);

$agent->setTools([
    new ReadFileTool(),                    // 对象
    'get_weather' => [                     // 闭包(旧格式)
        'description'  => '查询天气',
        'input_schema' => [...],
        'handler'      => function (array $in) { return '晴'; },
    ],
]);

use Ai\Agent\Tool\ParallelSafeToolInterface;

class ReadFileTool implements AgentToolInterface, ParallelSafeToolInterface
{
    public function isParallelSafe() { return true; }
}

use Ai\Agent\Tools\ClaudeCodeTools;

// 全部工具:Read / Write / Edit / Glob / Grep / Bash
$agent->setTools(ClaudeCodeTools::all([
    'workdir' => '/var/www/project',
]));

// 只读工具集(适合 plan 模式):Read / Glob / Grep
$agent->setTools(ClaudeCodeTools::readOnly([
    'workdir' => '/var/www/project',
]));

// 联网工具(默认不装配,需显式启用):web_fetch / web_search / translate
$agent->tools(ClaudeCodeTools::web());

// 模型侧:bash(command: "composer install", run_in_background: true)
//        → 立刻拿到句柄 id,继续干别的
// 之后:  bash_output(bash_id: "bg_1_ab12cd")   → 取增量输出
//        bash_output(action: "list")            → 列出全部后台任务
//        bash_output(bash_id: "...", action: "kill")  → 结束

use Ai\Agent\Tools\LspTool;
use Ai\Agent\Tools\PathSafety;

$agent->tools(['lsp' => new LspTool(
    new PathSafety('/var/www/project'),
    'intelephense', ['--stdio'],              // PHP;其它语言换 gopls / typescript-language-server 等
    ['rootPath' => '/var/www/project']
)]);
// action:definition 跳定义 / references 找引用 / hover 看签名 / symbols 列符号
// line 从 1 起(与 read_file、grep 一致),character 从 0 起(LSP 原生列号)

use Ai\Agent\Reflection\ReflectionManager;
use Ai\Agent\Reflection\LlmReflectionStrategy;

$rm = new ReflectionManager();
// 强烈建议接上兜底:模型判不出来时退回内置判据,而不是直接停
$rm->setStrategy(new LlmReflectionStrategy($ai, ['fallback' => $rm->defaultStrategy()]));

use Ai\Agent\Tools\NotebookEditTool;
use Ai\Agent\Tools\PathSafety;

$agent->tools(['notebook_edit' => new NotebookEditTool(new PathSafety('/var/www/project'))]);
// action=read 列出各 cell;replace / insert / delete 按 cell_index 改动

use Ai\Helpers\Translate;

// 数组入参返回数组,字符串入参返回字符串
$en = Translate::to('项目使用 CodeIgniter 3', 'en', ['from' => 'zh-Hans']);

// Edge 不可用时用库自带 LLM 兜底
$en = Translate::to($texts, 'en', ['from' => 'zh-Hans', 'ai' => $ai]);

$cm->dedupeFileReads();          // 返回被折叠的份数;compact() 内部会先自动调用

// 内置 token 数是字符估算;各模型分词规则不同,可注入精确计数器
$cm->setTokenizer(function ($text) { return my_tokenizer_count($text); });

use Ai\Helpers\Text;

Text::cutBytes($output, 1024);      // 限字节,不切开字符(工具输出、diff 用这个)
Text::cutChars($summary, 200);      // 限字符(给人看的摘要用这个)
Text::ellipsis($text, 200);         // 限字符并补省略号,未超长时不加
Text::isValidUtf8($text);           // 校验
Text::length($text);                // 字符数

$agent->setPermissionMode('plan');      // 只读模式
$agent->setPermissionMode('manual');    // 危险工具询问(默认)
$agent->setPermissionMode('bypass');    // 全部放行

use Ai\Agent\Permission\PermissionManager;

$pm = new PermissionManager('manual');
$pm->allowTool('read_file');                           // 全部放行 read_file
$pm->allowTool('write_file', ['path' => '/var/www/*']); // 只允许写指定目录
$pm->denyTool('bash', ['command' => 'rm -rf *']);       // 拒绝危险命令

$agent->getRuntime()->setPermission($pm);

$agent->setPermissionMode('manual');

$messages = [['role' => 'user', 'content' => '列出文件']];  // 业务层保存消息副本

$agent->onEvent(function ($event) {
    if ($event['type'] === 'tool_permission') {
        // 前端展示授权对话框
        echo "请求授权:{$event['name']}(" . json_encode($event['input']) . ")";
        $requestId = $event['request_id'];
    }
});

// 用 getRuntime()->run() 返回 AgentResult
$result = $agent->getRuntime()->run($messages);

// 在外部通过 approve() / deny() 恢复执行
if ($result->getStopReason() === 'permission_denied') {
    $requestId = $result->getExtra()['request_id'] ?? '';

    // 批准:传入之前保存的消息副本,Agent 继续执行
    $result = $agent->approve($requestId, $messages);
    // 或
    $result = $agent->deny($requestId, '不需要', $messages);
}

use Ai\Agent\Context\ContextManager;

$agent->getRuntime()->setContextManager(new ContextManager(
    $messages,
    [
        'maxTokens'  => 100000,   // 压缩阈值(默认 100k)
        'threshold'  => 0.8,      // 超过 80% 时触发
        'keepRecent' => 10,       // 保留最近 10 条消息
    ]
));

use Ai\Agent\Session\FileSessionStore;
use Ai\Agent\Session\SessionManager;

$store = new FileSessionStore('/tmp/agent_sessions');

$agent
    ->setSessionId('user-abc-123')
    ->setSessionManager(new SessionManager($store));

// 下次请求换一个新实例,只要 sessionId 一样就接得上——
// chat() 自己从会话里读上下文,不用手动把消息传回去
$agent->chat('接着上次说');

$agent->run($messages);

$session = $store->load('user-abc-123');
if ($session) {
    $agent->run($session->getMessages());     // run() 以会话里的消息为准
}

$agent
    ->setWorkdir('/var/www/project')
    ->setUserId('u_10086')          // 多租户场景的硬要求:按用户隔离
    ->setSessionId(Agent::newSessionId());

$agent->chat('记住我偏好深色主题');
// 换个新实例,只要 userId + sessionId 一致就接得上

> $agent->autoPersist(false);
> 

use Ai\Agent\SubAgent\SubAgentManager;
use Ai\Agent\Tools\ReadFileTool;
use Ai\Agent\Tools\PathSafety;

$sam = new SubAgentManager($ai);
$sam->register('code-reviewer', [
    'description' => '审查代码质量',
    'prompt'      => '你是代码审查专家,找出安全漏洞和性能问题。',
    'tools'       => [new ReadFileTool(new PathSafety('/var/www'))],
    'max_iter'    => 10,
]);

$agent->getRuntime()->setSubAgentManager($sam);

$sam->setBackgroundRunner(function ($task) {
    // 在 Swoole / Workerman 协程或队列 Worker 中异步执行
    return [
        'status'     => 'completed',
        'summary'    => '子任务完成',
        'iterations' => 5,
    ];
});

// 获取某次运行的完整 transcript
$transcript = $sam->getTranscript('sub_1_...');
// $transcript = [
//     'task_id'    => 'sub_1_...',
//     'agent'      => 'code-reviewer',
//     'task'       => '审查 Auth.php',
//     'status'     => 'completed',
//     'reason'     => 'end_turn',
//     'summary'    => '发现了 3 个问题...',
//     'messages'   => [...],           // 完整消息历史
//     'iterations' => 8,
//     'duration_ms'=> 12500.3,
//     'created_at' => 1700000000,
// ];

// 查询最近运行记录
$recent = $sam->recentRuns(10);

// 获取结果摘要(不含完整消息历史,适合反馈给主 Agent)
$result = $sam->getResult('sub_1_...');

$agent->setWorkdir('/var/www/project');   // 必须是 git 仓库根目录
// 模型调用:spawn_agent(agent="refactorer", task="重构 Auth.php", isolation="worktree")

$result = $sam->getResult('sub_1_...');
echo $result['diff'];        // 完整 unified diff,主工作区文件未被改动
echo $result['diff_stat'];   // ' src/Auth.php | 42 +++++-----'

// 快捷方式
$agent->setMaxBudget(5.0);                          // 预算 5 美元
$agent->getRuntime()->setMaxTokens(500000);          // 或按 token 数限制

// 精细配置
use Ai\Agent\Budget\BudgetManager;

$bm = new BudgetManager([
    'maxBudget'  => 5.0,                              // 最大预算(美元)
    'maxTokens'  => 500000,                           // 最大 token 数
    'pricing'    => ['prompt' => 5.0, 'completion' => 25.0, 'cached' => 0.5],
    'perMillion' => true,                             // 按每百万 token 计(官网价)
]);

$agent->getRuntime()->setBudget($bm);

$bm = new BudgetManager([
    'maxDuration'  => 300,   // 墙钟上限:最多跑 5 分钟
    'maxToolCalls' => 100,   // 工具调用次数上限
]);

$summary = $bm->summary();
// ['exceeded' => true, 'reason' => '超过墙钟上限(312.4s > 300s)',
//  'tokens' => 48210, 'cost' => 0.83, 'elapsed' => 312.4, 'tool_calls' => 37]

$result = $agent->getRuntime()->run($messages);

$result->getCost();          // 本次运行的估算成本
$result->getDuration();      // 毫秒
$result->getFilesChanged();  // 动过哪些文件
$result->toContract();       // 完整结构化结果,可直接 JSON 返回

$agent->getRuntime()->getLoop()->setModelRetries(3, 500);   // 重试 3 次,首次退避 500ms
$agent->getRuntime()->getLoop()->setModelRetries(0);        // 关掉

if ($result->getStopReason() === 'model_error') {
    echo $result->getText();                         // 挂掉之前它说过什么
    echo $result->getExtra()['error'];               // 为什么挂
    echo implode(',', $result->getFilesChanged());   // 已经改了哪些文件
}

use Ai\Agent\Loop\CancellationToken;

// 1. 同进程直接取消
$token = new CancellationToken();
$agent->setCancellation($token);
// …另一处(信号处理器、定时器):
$token->cancel('用户中断');

// 2. Web:浏览器一断就别再烧钱
$agent->setCancellation(CancellationToken::whenConnectionAborted());

// 3. 后台任务:另一个进程 touch 一下就能叫停
$agent->setCancellation(CancellationToken::whenFileExists("/tmp/stop-{$taskId}"));

// 4. 硬超时
$agent->setCancellation(CancellationToken::afterSeconds(600));

// 也可以直接叫停,没挂令牌会自动建一个
$agent->cancel('不用跑了');

$result = $agent->getRuntime()->run($messages);
if ($result->getStopReason() === 'user_cancel') {
    echo $result->getExtra()['reason'];      // 取消原因
    echo $result->getExtra()['resumable'];   // 能不能从检查点恢复
}

$handler = function (array $in, ToolContext $ctx) {
    foreach ($items as $item) {
        if ($ctx->isCancelled()) {
            return '已取消,处理了 ' . $done . ' 项';
        }
        // …
    }
};

$agent->setParallelTools(true);     // 启用并行

$agent->getRuntime()->setParallelRunner(function (array $tasks) {
    return \Swoole\Coroutine\parallel($tasks);
});

$agent->setToolTimeout(60);               // 全局超时 60 秒
$agent->getRuntime()->setToolTimeout(30);  // 或通过 Runtime 设置

$agent->setFallbackModels(['claude-sonnet', 'claude-haiku']);

$agent->setVerification([
    'edit_file'  => ['php -l {file}'],
    'write_file' => ['php -l {file}'],
    'test'       => ['vendor/bin/phpunit'],
]);

use Ai\Agent\Verification\VerificationManager;

$vm = new VerificationManager([
    'edit_file' => ['php -l {file}'],
]);
$results = $vm->verify('edit_file', ['file_path' => 'src/Auth.php']);
// $results[0]->isPassed()  => true / false
// $results[0]->getError()  => 错误信息

use Ai\Agent\Verification\PhpSyntaxVerifier;

$verifier = new PhpSyntaxVerifier();
$verifier->supports('write_file');   // true——只处理写文件类工具

$result = $verifier->verify([
    'tool_name' => 'write_file',
    'file_path' => 'src/Auth.php',
]);

if (!$result->isPassed()) {
    echo $result->getVerifierName();   // 'php_syntax'
    print_r($result->getErrors());
    // [['file' => 'src/Auth.php', 'line' => 42, 'message' => 'syntax error, unexpected token "{"']]
}

use Ai\Agent\Verification\GitDiffVerifier;
use Ai\Agent\Verification\SecurityVerifier;
use Ai\Agent\Verification\UnitTestVerifier;

$agent->addVerifier(new SecurityVerifier());
$agent->addVerifier(new UnitTestVerifier([
    'command' => 'composer test',
    'workdir' => '/var/www/project',
]));
$agent->addVerifier(new GitDiffVerifier([
    'workdir'      => '/var/www/project',
    'maxFiles'     => 10,     // 一次改超过 10 个文件 → 拦下
    'maxLines'     => 500,    // 一次改超过 500 行 → 拦下
    'protectPaths' => ['composer.json', '.github/'],
]));

$agent->useDefaultVerifiers([
    'test'     => 'composer test',      // 给了才挂 UnitTestVerifier
    'workdir'  => '/var/www/project',   // 给了才挂 GitDiffVerifier
    'maxFiles' => 10,
]);
bash
composer 
bash
php tests/smoke_test.php    # 全部类可加载/可实例化、继承链签名兼容
php tests/stream_test.php   # 40 个协议 × 普通对话 / 流式 / token 统计
php tests/tools_test.php    # 工具调用跨平台一致性
php tests/lib_test.php      # 并发批量 / Memory 并发安全 / 计价 / 日志注入
php tests/cli_test.php      # CLI 参数渲染与命令注入防护
php tests/ssrf_test.php     # SSRF 防护的全部已知绕过向量

composer test               # 依次跑上面全部六套
composer analyse            # PHPStan level 8 静态分析(全绿)
composer compat             # PHP 7.1 兼容性扫描(PHPCompatibility)
composer check              # 上面三样一起跑
php
$sec = new SecurityVerifier();
$sec->allow('exec');   // 比关掉整个验证器精确
php
$agent->setWorkspaceDir('/var/www/project');

// 或手动创建
use Ai\Agent\Workspace\WorkspaceManager;

$wm = new WorkspaceManager('/var/www/project');
$wm->refresh();
echo $wm->getBranch();        // 'main'
echo $wm->getProjectName();   // 'project'
print_r($wm->getModified());  // ['src/Auth.php']
php
$agent->loadSkills('/path/to/skills');

// 或手动创建
use Ai\Agent\Skill\SkillManager;

$sm = new SkillManager();
$sm->register('deploy', [
    'description'  => '部署项目到生产环境',
    'content'      => "# 部署流程\n1. 构建...",
    'allowedTools' => ['Bash(git *)'],
]);
$agent->setSkillManager($sm);
php
$handler = $sm->getUseSkillHandler();
echo $handler(['skill' => 'brand-archetype-system']);
php
// 程序里直接读
$sm->readResource('brand-archetype-system', 'references/core-archetypes/06-luxe-considered.md');
php
$found = $agent->discoverSkills('/path/to/skills');   // ['wordpress', 'deploy', 'nginx']
php
// 按文件路径找匹配的技能
$sm->forFile('/var/www/wp-content/plugins/foo.php');   // ['wordpress' => SkillDefinition]

// 直接激活——省掉模型自己判断该不该 use_skill 这一步
$agent->activateSkillsForFile('/var/www/wp-content/plugins/foo.php');   // ['wordpress']

// 只加载正文、不激活(不合并 allowed-tools)
$sm->loadByName('wordpress');
php
$sm->setEnumInSchema(false);
php
$child = $sm->subset(['php-development', 'seo']);   // 不继承激活状态
php
$agent->loadInstructions('/var/www/project');

// 或手动创建
use Ai\Agent\Instruction\InstructionManager;

$im = new InstructionManager();
$im->loadFromTree('/var/www/project');         // 加载 .claude/ .ai/ 根目录的指令
$im->loadFromDir('/var/www/project/src');      // 子目录级指令(优先级更高)
$agent->setInstructionManager($im);
php
// 自定义文件名
$im->setFilenames(['CLAUDE.md', 'AGENTS.md', '.ai/AGENTS.md']);
php
$mm->connect('remote');            // 单独连一个,失败返回 false 而不抛异常
$mm->isConnected('remote');        // true
$mm->discoverTools('remote');      // 动态发现工具列表,未连接时自动先连
$mm->disconnect('remote');         // 单独断开
print_r($mm->status());
// ['fs' => ['connected' => false, 'transport' => 'stdio'],
//  'remote' => ['connected' => true, 'transport' => 'http']]
echo $mm->getLastError();          // 最近一次连接失败的原因
php
$agent->setMemoryDir('/tmp/agent_memory');

// 或手动创建
use Ai\Agent\Memory\MemoryManager;

$mm = new MemoryManager('/tmp/agent_memory');
$mm->remember('user', '用户喜欢 PHP');
$mm->remember('project', '项目使用 CodeIgniter 3');
$mm->remember('session', '当前正在修登录');
$mm->remember('task', '正在修改 Auth.php');
$mm->remember('agent', '上次尝试方案 A 失败');
$agent->setMemoryManager($mm);
php
$mm->read('user');        // 读取
$mm->write('user', '仅此一条');  // 覆盖
$mm->forget('user');      // 清空
$mm->clearAll();          // 清空全部作用域
$mm->forPrompt();         // 生成注入系统提示词的 <memory> 块
php
$retriever = $mm->retriever();

$hits = $retriever->retrieve('登录接口报 401');
// [['scope' => 'project', 'line' => 3, 'text' => '登录走 JWT,密钥在 config/jwt.php', 'score' => 62.5], ...]

echo $retriever->forPrompt('登录接口报 401');
// <memory-relevant query="登录接口报 401">
// [project] 登录走 JWT,密钥在 config/jwt.php
// </memory-relevant>

$mm->retrieve('登录 401');              // 等价快捷方法
$mm->forPromptRelevant('登录 401');     // 等价快捷方法
$retriever->search('JWT', 'project');   // 纯关键词搜索,不打分
php
$retriever->setScorer(function ($query, $text) use ($ai) {
    return cosineSimilarity($ai->embed($query), $ai->embed($text)) * 100;
});
$retriever->setTopK(3)->setMinScore(20.0);
php
$retriever->compress('session', 20);   // 只保留最近 20 条,返回删除条数
$retriever->expire('agent', 30);       // 删除 30 天前的条目
text
- [2026-09-03] (#9bda3a) 项目使用 CodeIgniter 3
php
$mm->remember('project', '项目使用 CodeIgniter 3');   // 写入带 id 的条目
$mm->forgetById('project', '9bda3a');                 // 按 id 精确删
$mm->findByPattern('project', 'CodeIgniter');         // 按子串找候选(不删)
php
$agent->autoConsolidate(true);   // 每次 run()/chat() 后从结果提炼候选、去重后写入
php
$agent->setCheckpointDir('/tmp/checkpoints');

// 或手动创建
use Ai\Agent\Checkpoint\CheckpointManager;

$cm = new CheckpointManager('/tmp/checkpoints');
$cm->save('task_1', 5, $messages, ['extra' => 'data']);
$latest = $cm->loadLatest('task_1');
echo $latest->getIteration();  // 5
php
// 自定义保留数
$cm = new CheckpointManager('/tmp/checkpoints', [
    'maxCheckpoints' => 10,
]);

// 清理超过 7 天的检查点
$cm->cleanExpired('task_1', 7);
php
$agent->setCheckpointDir('/tmp/checkpoints');

$messages = $agent->recoverFromCrash('task_1');
if ($messages !== null) {
    // 恢复成功,继续执行
    $agent->run($messages);
    $text = $agent->lastText();          // run() 返回 void,文本从这里取
} else {
    // 无可用检查点,从头开始
    $agent->run([['role' => 'user', 'content' => '请检查项目']]);
}
php
$agent->setCheckpointDir('/var/data/checkpoints');
$agent->setPlanDir('/var/data/plans');
$plan = $agent->plan('迁移数据库', ['备份', '改表', '校验']);

$agent->run([['role' => 'user', 'content' => '开始迁移']]);
// 中途崩了……

// 新进程恢复
$runtime = $agent->getRuntime();
$messages = $runtime->recover('task_1');
echo $runtime->getGoal();                        // '迁移数据库' —— 已还原
echo $runtime->getPlan()->progress();            // 计划状态与各步骤状态原样还原
print_r($runtime->getLastCheckpoint()->getExtra()['workspace']);
// ['dir' => '/var/www/project', 'branch' => 'main', 'modified' => ['db/schema.sql']]
php
use Ai\Agent\Queue\AgentQueue;

$queue = new AgentQueue();
$task = $queue->dispatch('检查并修复项目', $runtime, $messages, 'sess_1');

// 逐个处理待执行任务
while ($queue->hasPending()) {
    $result = $queue->processNext();
    // $result->getText() 获取最终回复
}

// 或处理指定任务
$result = $queue->process('task_xxx');

// 任务控制
$queue->cancel('task_xxx');    // 取消
$queue->resume('task_xxx');    // 恢复暂停的任务

echo $task->getStatus();  // queued / running / completed / failed / cancelled
php
use Ai\Agent\Planning\PlanManager;

$pm = new PlanManager('/var/data/plans');   // 传空字符串则只放内存

$plan = $pm->createPlan('为 Auth 模块补充单元测试', [
    'steps' => [
        '阅读 src/Auth.php 理清分支',
        '编写 tests/AuthTest.php',
        '运行 phpunit 并修复失败用例',
    ],
    'risks' => ['依赖外部 Redis,测试需要 mock'],
]);

$pm->start($plan->getId());
$step = $pm->getCurrentStep($plan->getId());   // PlanStep:第 1 步
$pm->completeStep($plan->getId(), $step->getId(), '已梳理出 4 个分支');
$pm->advance($plan->getId());                  // 推进到下一步

echo $plan->progress();    // 33(百分比)
echo $plan->toSummary();   // 注入系统提示词用的简明摘要
php
use Ai\Agent\Planning\PlanExecutor;

$executor = new PlanExecutor($pm, ['mode' => PlanExecutor::MODE_DEPENDENCY]);
$result = $executor->executeAll($plan->getId(), function ($step, $plan) {
    // 在这里让 Agent 真正执行这一步,返回字符串结果或抛异常表示失败
    return $agent->ask($step->getAction())->getText();
});
// $result = ['completed' => 2, 'failed' => 1, 'skipped' => 0, 'status' => 'failed']
php
use Ai\Agent\Planning\PlanReview;

$review = new PlanReview($pm);
$r = $review->review($plan->getId());
// $r = ['status' => 'affected', 'progress' => 33, 'issues' => [...], 'suggestions' => [...], ...]

// 审查后追加/插入/移除步骤
$review->reviewAndAdjust($plan->getId(), [
    'append' => ['补充 README 中的测试说明'],
], '发现文档未同步');

$review->detectDependencyCycle($plan);   // 返回环上的步骤 ID,无环则为空数组
php
$agent->setPlanDir('/var/data/plans');            // 可选,不设则只放内存
$plan = $agent->plan('重构支付模块', [
    '读懂现有 Pay.php',
    '拆出 PaymentGateway 接口',
    '跑测试确认行为不变',
]);

$agent->run([['role' => 'user', 'content' => '开始重构']]);
echo $agent->getPlan()->progress();   // 执行到哪一步了
php
$state = new \Ai\Agent\Task\TaskState(['goal' => '重构支付模块']);
$state->setPlan($plan);
file_put_contents('/var/data/task.json', $state->toJson());

// 恢复
$restored = \Ai\Agent\Task\TaskState::fromJson(file_get_contents('/var/data/task.json'));
$plan = $restored->restorePlan();   // Plan 对象,步骤状态原样还原
php
use Ai\Agent\Reflection\ReflectionManager;

$rm = new ReflectionManager(['maxRounds' => 5]);
$result = $rm->reflect($messages, '让 phpunit 全部通过');

if ($rm->shouldContinue($result)) {
    echo $result->getReason();       // '最近一次工具执行报错:Fatal error ...'
    echo $result->getNextAction();   // '分析上述错误并修复后重新运行测试'
    echo $result->toPrompt();        // 直接注入下一轮 prompt 的文本
}
php
$rm->setStrategy(function (array $messages, $goal) use ($ai) {
    $verdict = $ai->ask("目标:{$goal}\n以上执行是否已完成?回答 DONE 或说明缺什么");
    return strpos($verdict, 'DONE') !== false
        ? \Ai\Agent\Reflection\ReflectionResult::completed('模型判定已完成')
        : \Ai\Agent\Reflection\ReflectionResult::continuing($verdict, '按上述说明继续');
});
php
$agent->enableReflection(['maxRounds' => 5]);
$agent->setGoal('让 composer test 全部通过');

$agent->onEvent(function ($e) {
    if ($e['type'] === 'reflection') {
        echo $e['success'] ? "反思:目标已达成\n" : "反思:{$e['reason']}\n";
    }
});

$agent->run([['role' => 'user', 'content' => '修一下失败的测试']]);
php
use Ai\Agent\Agent;

$agent = Agent::create($ai)
    ->workdir('/var/www/project')
    ->codeAgent();

$result = $agent->task('把这个项目的登录系统改造成支持 Google OAuth,并完成测试');

print_r($result->toContract());
// [
//   'status'        => 'completed',
//   'summary'       => '已接入 Google OAuth 并补充测试',
//   'files_changed' => ['src/Auth.php', 'tests/AuthTest.php'],
//   'tests'         => ['passed' => true],
//   'verification'  => ['passed' => true],
//   'artifacts'     => ['artifact://task_1/test-report.json'],
//   'cost' => 0.042, 'iterations' => 14, 'duration_ms' => 68210.5,
// ]
php
$agent = Agent::create($ai)
    ->workdir(__DIR__)
    ->codeAgent(['test' => 'composer test'])
    ->tools(['my_tool' => $myTool])              // 追加工具(不覆盖已有的)
    ->skills('/path/to/skills')                  // 装载技能
    ->agents(['dba' => [                         // 注册自定义子 Agent
        'description' => '数据库结构与索引优化',
        'tools'       => ['read_file', 'bash'],  // 写工具名即可,从父工具集里挑
    ]]);

$result = $agent->task('修复登录 Bug');          // 编排层自己选策略
$handle = $agent->dispatch('扫描整个项目');      // 后台执行,立即返回 task_id
$agent->resume($handle['task_id']);              // 查后台句柄,或从检查点恢复
php
$agent->useToolDiscovery(['read_file', 'grep', 'glob']);

// 模型调用:search_tools(query: "database")
//   → 找到 sql_query / db_schema,自动启用,之后可直接调用
php
$policy = $agent->permissionPolicy();
$policy->layer('global')
    ->allow('Bash(git *)')
    ->deny('Bash(rm -rf *)')
    ->deny('Write(.env)');
$policy->layer('task')->allow('Bash(php *)');

$agent->applyPermissionPolicy();

$policy->check('Bash', 'git status');    // 'allow'
$policy->check('Bash', 'rm -rf /');      // 'deny'
$policy->check('Bash', 'curl x.com');    // 'ask' —— 没人明确允许
$policy->explain('Bash', 'rm -rf /');    // ['decision' => 'deny', 'layer' => 'global', 'rule' => 'Bash(rm -rf *)']
php
$log = $agent->eventLog('/var/data/events');   // 创建后自动接上事件回调

// 客户端带着 Last-Event-ID 重连
$missed = $log->sinceId($lastEventId);
echo EventLog::toSse($missed);                 // 直接输出 SSE 帧

$log->since(42);                // 按 sequence 补发
$log->ofTask('task_1');         // 某个任务的全部事件
$log->ofType('tool_call');      // 按类型过滤
$log->lastSequence();           // 当前最新序号
php
$scheduler = $agent->scheduler([
    'max_tasks'             => 50,
    'max_concurrent'        => 4,
    'max_subagents'         => 4,
    'max_background_agents' => 2,
    'max_parallel_tools'    => 8,
    'maxRetries'            => 2,
]);

$scheduler->submit('task_1', '扫描安全问题', AgentScheduler::PRIORITY_HIGH);
$scheduler->submit('task_2', '更新文档', AgentScheduler::PRIORITY_LOW);

$next = $scheduler->next();          // 'task_1' —— 高优先级先跑
$scheduler->start($next);
$scheduler->finish($next, false);    // 失败 → 自动重新入队(还有重试次数时)
php
$router = $agent->modelRouter([
    'cheap'    => 'claude-haiku-4-5-20251001',
    'standard' => 'claude-sonnet-5',
    'premium'  => 'claude-opus-5',
]);

$router->route(['agent' => 'explorer']);                     // cheap
$router->route(['agent' => 'coder']);                        // premium
$router->route(['task' => '重构整个认证系统']);               // premium(复杂度高)
$router->route(['agent' => 'coder', 'budget_left' => 0.05]);  // cheap(预算见底,先把任务跑完)
php
$agent->platforms([
    'deepseek' => ['api_key' => $deepseekKey],
    'moonshot' => ['api_key' => $moonshotKey],
    'openai'   => ['api_key' => $openaiKey],
])->modelRouter([
    'cheap'    => 'deepseek-chat',      // explorer 之类的杂活
    'standard' => 'moonshot-v1-32k',
    'premium'  => 'gpt-4o',             // coder / reviewer
]);
php
$agent->getRuntime()->getSubAgentManager()->setRouteContext(function ($def, $task) use ($budget) {
    return ['budget_left' => $budget->remainingRatio()];
});
php
$artifacts = $agent->artifacts('/var/data/artifacts');

$ref = $artifacts->put('task_123', 'test-report.json', $reportJson);
// 'artifact://task_123/test-report.json'

$artifacts->preview($ref, 500);      // 只给模型看开头
$artifacts->get($ref);               // 需要细节时再取全文
$artifacts->listFor('task_123');     // 这个任务产出了什么
$artifacts->summarize('task_1', 'out.txt', $output, 5);
// '已保存到 artifact://task_1/out.txt(12.4 KB):\nFAILURES!\n…'
php
$agent = Agent::create($ai)
    ->workdir(__DIR__)
    ->tools($tools)
    ->setPlanManager(new PlanManager())
    ->setSubAgentManager($sam)
    ->modelDrivenTools();      // ← 挂上 update_plan 与 delegate

$agent->run([['role' => 'user', 'content' => '给项目加上 OAuth 登录并跑通测试']]);
php
$agent->onEvent(function ($e) {
    if ($e['type'] === 'plan_updated') {
        echo "计划改到第 {$e['version']} 版,共 {$e['steps']} 步,进度 {$e['progress']}%\n";
    }
});
php
$agent = Agent::create($ai)->workdir($dir)->codeAgent();          // 用默认提示词
$agent = Agent::create($ai)->setSystem('...')->codeAgent();       // 用你自己的
echo \Ai\Agent\CodeAgentPrompt::build(['test' => 'composer test']);  // 看看默认写了什么
php
$agent = (new Agent($ai))->setWorkdir('/var/www/project')->codeAgent();

$result = $agent->task('分析项目中的认证、支付、SEO');
// 自动识别成三路并行 → 派 explorer 分头调查 → 汇总摘要

echo $agent->orchestrator()->lastDecision()->toSummary();
// 策略:parallel —— 识别到 3 个互不相关的子任务
php
$agent->useModelStrategy();                            // 用当前模型决策
$agent->useModelStrategy(['model' => 'gpt-4o-mini']);  // 决策用便宜的小模型
php
$agent->onEvent(function ($e) {
    if ($e['type'] === 'strategy_decision') {
        echo "{$e['strategy']}:{$e['reason']}\n";
    }
});
php
$decision = $agent->orchestrator()->decide('重构认证系统');
if ($decision->is(\Ai\Agent\Orchestrator\ExecutionStrategy::PLAN)) {
    $result = $agent->orchestrator()->execute('重构认证系统', $decision);
}
php
$agent->orchestrator()->selector()->setResolver(function ($task, $context) use ($ai) {
    // 返回 StrategyDecision;返回 null 则退回规则判断
});

// 或直接关掉某类自动行为
$agent->orchestrator()->selector()->setAutoDelegate(false)->setAutoPlan(false);
php
use Ai\Agent\SubAgent\BuiltinAgents;

BuiltinAgents::registerAll($sam);                    // 六个全装
BuiltinAgents::register($sam, ['explorer', 'tester']); // 只装两个
BuiltinAgents::isReadOnly('explorer');               // true
php
$sam->register('reviewer', [
    'description'     => '代码审查与安全审查',   // 也是自动委派的匹配依据
    'prompt'          => '你是代码审查者……',
    'tools'           => [...],
    'disallowedTools' => ['write_file', 'edit_file'],
    'model'           => 'claude-sonnet-5',      // 该角色单独用的模型
    'api_key'         => $anthropicKey,          // 该角色单独用的平台凭据(见「跨平台编排」)
    'permissionMode'  => 'manual',
    'maxTurns'        => 15,
    'skills'          => ['php-development'],
    'mcpServers'      => ['fs'],
    'hooks'           => $hooks,
    'memory'          => '/var/data/memory/reviewer',
    'background'      => false,
    'isolation'       => 'worktree',
]);
php
$sam->setParentTools($parentTools);   // 父 Agent 只有 read_file / grep / glob

$sam->register('greedy', ['tools' => ['read_file' => …, 'write_file' => …, 'bash' => …]]);
$sam->resolveTools($sam->get('greedy'));   // 只剩 read_file —— 父没有的拿不到
php
$agent = Agent::create($ai)->agents([
    'planner'  => ['description' => '任务规划', 'model' => 'gpt-4o',          'api_key' => $openaiKey],
    'coder'    => ['description' => '写代码',   'model' => 'deepseek-chat',   'api_key' => $deepseekKey],
    'reviewer' => ['description' => '代码审查', 'model' => 'moonshot-v1-32k', 'api_key' => $moonshotKey],
]);
php
$agent->platforms([
    'deepseek' => ['api_key' => $deepseekKey],
    'moonshot' => ['api_key' => $moonshotKey],
    'openai'   => ['api_key' => $openaiKey],
])->agents([
    'coder'    => ['description' => '写代码',   'model' => 'deepseek-chat'],
    'reviewer' => ['description' => '代码审查', 'model' => 'moonshot-v1-32k'],
    'planner'  => ['description' => '任务规划', 'model' => 'gpt-4o'],
]);
php
$graph->syncFrom($taskManager);   // 同步任务与状态,图结构不变
php
$handle = $agent->dispatch('扫描整个项目的安全问题');
// ['task_id' => 'task_1_ab12cd34', 'status' => 'running', 'mode' => 'fork', 'background' => true]

$agent->taskStatus($handle['task_id']);
php
use Ai\Agent\Orchestrator\BackgroundDispatcher;

$dispatcher = new BackgroundDispatcher([
    'resultDir' => '/var/data/bg',      // fork 档必须配:子进程改的内存父进程看不到
    'runner'    => function (array $payload) { /* 投递到队列 */ return null; },
]);
$agent->orchestrator()->setDispatcher($dispatcher);
php
use Ai\Agent\Orchestrator\ParallelAgentExecutor;

$executor = new ParallelAgentExecutor($sam, ['maxConcurrency' => 4]);
$results = $executor->run([
    ['agent' => 'explorer', 'task' => '分析认证模块'],
    ['agent' => 'explorer', 'task' => '分析支付模块'],
    ['agent' => 'explorer', 'task' => '分析 SEO 相关代码'],
]);
echo $executor->mode();   // 'runner' | 'fork' | 'sequential'
php
$summary = $agent->orchestrator()->aggregator()->aggregate($results);

$summary['summary'];          // 合并摘要(给主 Agent 的就是这个)
$summary['findings'];         // 各路结论(已按长度截断)
$summary['files'];            // 提到的文件,去重
$summary['errors'];           // 未完成的那几路,带原因
$summary['recommendations'];  // 抽出来的建议
$summary['transcripts'];      // task_id 列表,要看细节按这个查
$summary['stats'];            // ['total' => 3, 'completed' => 2, 'failed' => 1]
php
$aggregator->setSummarizer(function (array $results) use ($ai) {
    return $ai->ask('把以下多路调查结果合并成一段摘要:' . json_encode($results));
});
php
$sam->setTranscriptDir('/var/data/transcripts');

$runId = $sam->runSync('explorer', '调查登录流程');
// 另一个进程也读得到
$sam2->setTranscriptDir('/var/data/transcripts');
$sam2->getTranscript($runId);

// 子 Agent 跑到一半被截断(迭代上限、权限被拒)时接着干,而不是从头再来
$newRunId = $sam->resume($runId, '继续看看权限校验');
$sam->getTranscript($newRunId)['resumed_from'];   // 原 runId
php
$agent->useVerificationGate();                      // 按任务类型自动选验证链
$agent->setCompletionCriteria([                     // 完成 = 一组可检查的条件
    \Ai\Agent\Orchestrator\CompletionCriteria::VERIFICATION_PASSED,
    \Ai\Agent\Orchestrator\CompletionCriteria::NO_PENDING_STEPS,
    \Ai\Agent\Orchestrator\CompletionCriteria::NO_PENDING_ERRORS,
]);

$result = $agent->task('修复登录 401');
$outcome = $agent->checkCompletion($result, '修复登录 401');

if (!$outcome['completed']) {
    echo $outcome['prompt'];
    // 任务尚未达成完成条件:
    // - 验证未通过
    // - 计划里还有 2 个步骤未完成:跑测试、更新文档
    $agent->ask($outcome['prompt']);   // 带着原因继续干
}
php
use Ai\Agent\Verification\VerificationGate;
use Ai\Agent\Verification\VerificationPolicy;

$gate = new VerificationGate($vm, VerificationPolicy::security());
$outcome = $gate->check(['file_path' => 'src/Auth.php']);

$outcome['passed'];    // 闸门放行了吗
$outcome['failed'];    // 哪几步没过,
php
$criteria->addCriterion('has_changelog', function (array $context) {
    return ['met' => !empty($context['changelog']), 'reason' => '还没写 changelog'];
});
php
$handoff = $team->handoff('developer', 'dba', '慢查询定位到索引缺失', [
    'task_id'         => 'task_9',
    'context_summary' => '已定位到 UserRepo::findByEmail,全表扫描 12 万行',
]);

// DBA 处理完交回去
$team->handoffBack($handoff, '索引已补,慢查询从 3s 降到 20ms');

$team->handoffChain('task_9');   // ['developer → dba', 'dba → developer']
php
$req  = AgentMessage::request('coder', 'dba', '这张表有索引吗');
$resp = AgentMessage::respondTo($req, '没有,需要补');
$resp->getReplyTo();   // $req 的 ID
php
$plan = $pm->createPlan('迁移数据库', ['steps' => ['备份', '改表']]);
$plan->getVersionLabel();   // 'plan_v1'

$pm->modifyPlan($plan->getId(), ['append' => ['校验数据']], '发现漏了校验');
$pm->getPlan($plan->getId())->getVersion();   // 2

$pm->versions($plan->getId());          // 历史版本快照
$pm->getVersion($plan->getId(), 1);     // 取回 v1,仍然是两步
$pm->diffVersions($plan->getId(), 1, 2);
// ['added' => ['校验数据'], 'removed' => [], 'reason' => '发现漏了校验']
php
use Ai\Agent\Workspace\WorkspaceSnapshot;

$before = WorkspaceSnapshot::capture('/var/www/project');
// …Agent 干活…
$after = WorkspaceSnapshot::capture('/var/www/project');

$diff = WorkspaceSnapshot::diff($before, $after);
$diff['added'];            // 新增的文件
$diff['modified'];         // 改动的文件
$diff['deleted'];          // 消失的文件
$diff['branch_changed'];   // 分支变了没有
$diff['content_changed'];  // diff 哈希变了没有
php
$samW->mergeWorktreeRun($runId, true);   // 先试打(git apply --check)
$samW->mergeWorktreeRun($runId);         // 真打
// ['applied' => true, 'reason' => 'applied']

$samW->discardWorktreeRun($runId, '方案不对');   // 留痕:这份 diff 被看过并否决了
php
$sm->onEvent(function ($e) { echo $e['type'], "\n"; });
// skill_discovered / skill_loaded / skill_activated / skill_deactivated

$sm->deactivate('deploy');   // 停用:后续轮次不再注入,allowed-tools 一并重算
php
$sm->checkRequirements('deploy', ['bash']);
// ['satisfied' => false, 'missing' => ['ssh', 'skill:docker']]
php
$im->setProjectRoot('/var/www/project');
$im->discoverFor('/var/www/project/src/Admin/User.php');
// 依次加载:project/CLAUDE.md → project/src/AGENTS.md → project/src/Admin/AI.md
php
$consolidator = $agent->memoryConsolidator();

$consolidator->propose('project', '登录走 JWT,密钥在 config/jwt.php', ['confidence' => 0.9]);
$consolidator->proposeFromReflection($reflectionResult);   // 反思结论值得记
$consolidator->proposeFromResult($agentResult, 'task');    // 只取首段,不整段塞

$written = $consolidator->consolidate();   // 去重 + 滤低置信度 + 按置信度排序 + 截断
php
$consolidator->setFilter(function (array $candidate) {
    return strpos($candidate['content'], '密码') === false;
});
php
use Ai\Agent\Team\AgentRole;

$team = $agent->team([
    AgentRole::developer(),
    AgentRole::tester(),
    AgentRole::reviewer(),
]);

// 单独分派
$result = $team->assign('developer', '实现登录接口');
echo $result['status'];   // 'completed'
echo $result['text'];

// 流水线:前一环的输出作为后一环的输入
$results = $team->pipeline('给 Auth 模块补测试', ['developer', 'tester', 'reviewer']);

echo $team->toSummary();
// [developer] 给 Auth 模块补测试(completed,4 轮):已补充 3 个测试用例…
// [tester] …
php
$team->addMember(new AgentRole('dba', [
    'description' => '数据库结构与查询优化',
    'prompt'      => '你是 DBA,只负责表结构与索引,不改业务代码。',
    'tools'       => ['read_file', 'bash'],   // 只给这两个工具
    'maxIter'     => 8,
]));
php
use Ai\Agent\Team\AgentMessage;

$team->send(AgentMessage::bug('tester', 'developer', 'AuthTest::testLogin 失败:期望 true 实际 false', [
    'file' => 'tests/AuthTest.php',
    'line' => 42,
]));

$team->broadcast('需求已冻结,不要再改接口签名');

$bus = $team->communication();
$bus->unreadCount('developer');            // 未读条数
$bus->peek('developer');                   // 看但不取走
$bus->inbox('developer');                  // 取走(标记已读)
$bus->history(AgentMessage::TYPE_BUG);     // 全量历史,可按类型过滤
$bus->between('tester', 'developer');      // 两个角色之间的往来