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'); // 两个角色之间的往来