大家好,把手机静音。今天我们不聊那些“Hello World”式的入门教程,也不去吹捧那些动不动就要几千美元显卡成本的 Python 框架。今天我们要聊聊一个可能让某些人把咖啡喷在显示器上的话题:用 PHP 写 AI 智能体。
你没听错,就是那个跑在 Apache/Nginx 上、处理电商订单、在 Laravel 里 var_dump 的 PHP。现在,我们要让它去处理大语言模型(LLM)的编排、工具调用,甚至是长短期记忆。
想象一下,LLM 就像一个喝醉了诗人的大脑,它能吐出绝妙的句子,但你也可能让它去执行 rm -rf /。这时候,你需要一个清醒的“驾驶舱”,而 PHP,就是那个不仅不会吐,还能精准控制方向盘的仪表盘。
第一部分:为什么是 PHP?这不仅仅是为了省钱
在深入代码之前,我得先给你们洗洗脑。有人说:“PHP 已经老了,Python 才是 AI 的原生语言。”这话有理,但很片面。
想象一下,你要建一栋摩天大楼。Python 是那种拿着激光切割机的小个子工人,精确、敏捷,但搭建整个脚手架太慢。PHP 呢?PHP 是那个虽然看起来有点邋遢,但手里有一把巨大的扳手和一卷万能胶水的工头。它擅长把东西粘在一起,擅长处理请求,擅长把数据塞进数据库。
在 AI 智能体的世界里,大部分工作是“脏活累活”:调用外部 API、解析 JSON、连接数据库、读写文件。这些东西在 PHP 里,那是熟能生巧,甚至比 Python 更快——因为 PHP 的启动开销极低,对于需要频繁轮询 LLM API 的智能体来说,PHP 就像是一辆带涡轮增压的轿车,起步即加速。
而且,PHP 8.x 引入了 JIT 编译、联合类型、命名参数,它早就不是十年前那个只有面向过程和弱类型警告的 PHP 了。现在,它足够强壮,能撑起复杂的编排逻辑。
第二部分:智能体的“内脏”——编排与工具调用
什么是智能体?智能体不是那个只会鹦鹉学舌的聊天机器人。智能体是一个循环。它看、它思考、它行动,它观察结果,然后再次思考。
在 PHP 里,这个循环不需要 React,不需要 Vue,我们只需要 while 循环。
1. 拆解 LLM 的输出:解析 JSON 的艺术
LLM 最擅长的就是给你一个漂亮的 JSON 对象,但这并不代表它每次都做得对。如果 JSON 格式有一点点偏差,json_decode 就会返回 null,然后你的智能体就会崩溃。
这就是 PHP 发挥作用的地方。我们不仅要解码,还要确保解码成功,并给智能体提供足够清晰的错误信息。
让我们看一段代码,这是智能体的“大脑皮层”:
<?php
namespace AgentCore;
class AgentBrain
{
private string $apiKey;
private string $model;
public function __construct(string $apiKey, string $model = 'gpt-4')
{
$this->apiKey = $apiKey;
$this->model = $model;
}
/**
* 核心思考逻辑:发送提示词给 LLM,并处理返回值
*/
public function think(string $prompt, array $tools = []): array
{
$messages = [
['role' => 'system', 'content' => "You are a helpful AI agent. You have access to tools if listed. Return JSON strictly."],
['role' => 'user', 'content' => $prompt]
];
// 构建 API 请求
$payload = [
'model' => $this->model,
'messages' => $messages,
'tools' => $tools // 传入工具定义,让 LLM 知道它能做什么
];
// 发送请求 (假设我们有一个简单的 HTTP 客户端)
$response = $this->callLLM($payload);
// 解析 LLM 的回复
$content = $response['choices'][0]['message']['content'] ?? '';
// 这里的魔术在于,我们假设 LLM 必须返回 JSON,包含 action 和 params
$data = json_decode($content, true);
if (json_last_error() !== JSON_ERROR_NONE) {
// 如果解析失败,告诉 LLM 它的 JSON 写错了,让它重试
return [
'status' => 'retry',
'error' => 'Invalid JSON: ' . json_last_error_msg(),
'raw_content' => $content
];
}
return ['status' => 'success', 'data' => $data];
}
private function callLLM(array $payload): array
{
// 这里应该是 cURL 或者 Guzzle 的代码,为了简洁省略
// 实际生产中,记得加上代理和重试逻辑
return ['choices' => [['message' => ['content' => '{"action": "get_weather", "params": {"city": "Beijing"}}']]]];
}
}
看到了吗?PHP 的 json_decode 就像是一个严格的翻译官。如果 LLM 给出的 JSON 里有那个讨厌的尾随逗号,PHP 会直接报错,并把错误信息反馈给智能体,让它去修正。这比那些默默吞掉错误的框架要安全得多。
2. 工具调用:让 LLM 变得“能动手”
LLM 本身只能聊天,它不能查天气,不能买票。我们需要给它装上“手脚”。这就是“工具调用”。
在 PHP 中,工具就是一个函数,或者一个闭包。当 LLM 决定调用 get_weather(city) 时,PHP 的任务就是执行这个函数,拿到结果,再把结果喂回给 LLM。
这是智能体的核心逻辑,我们称之为 ActionExecutor:
<?php
namespace AgentTools;
class ActionExecutor
{
// 工具注册表:函数名 -> 实际执行的闭包/函数
private array $tools = [
'get_weather' => function (array $params) {
// 模拟数据库查询或 API 调用
if (!isset($params['city'])) {
return json_encode(['error' => 'City parameter missing']);
}
// 假装我们在查数据库
return json_encode(['city' => $params['city'], 'temp' => '24°C', 'condition' => 'Sunny']);
},
'search_db' => function (array $params) {
return json_encode(['results' => ['Record #1', 'Record #2']]);
}
];
/**
* 执行工具
*/
public function execute(string $toolName, array $params)
{
if (!isset($this->tools[$toolName])) {
return json_encode(['error' => "Tool '$toolName' not found"]);
}
// PHP 的闭包执行非常优雅
return call_user_func($this->tools[$toolName], $params);
}
}
这里的 call_user_func 是 PHP 的经典功能,它允许我们动态地把字符串 $toolName 变成实际执行的代码。这就意味着,你的智能体不需要重新加载代码,只需要在内存里注册新的函数,它就能瞬间掌握新技能。
第三部分:记忆——短期与长期的博弈
一个聪明的智能体不能像金鱼一样,问完一个问题就忘。它需要记忆。
在 AI 领域,记忆分两种:短期记忆和长期记忆。
1. 短期记忆:上下文窗口
短期记忆就是 Prompt 里的那堆历史消息。当用户问“北京的天气怎么样?”时,智能体得记得刚才它回答了“上海怎么样?”。PHP 在这里就是一个消息队列管理器。
class ShortTermMemory
{
private array $messages = [];
public function addMessage(string $role, string $content): void
{
$this->messages[] = [
'role' => $role,
'content' => $content
];
}
/**
* 获取上下文,但在智能体调用工具后,需要把工具结果加进去
*/
public function getContext(int $limit = 10): array
{
// PHP 的 array_slice 很适合做这种切片操作
return array_slice($this->messages, -$limit);
}
public function addToolResult(string $toolName, string $result): void
{
// 把工具执行的结果格式化成消息
$this->messages[] = [
'role' => 'assistant', // 上一步是助手
'content' => "[Tool Call: $toolName] Result: $result"
];
}
}
这段代码虽然简单,但它构成了智能体对话的骨架。它确保了 LLM 能看到它刚才做的“蠢事”和产生的“后果”。
2. 长期记忆:向量数据库与 PHP 的结合
短期记忆有上限(Token 限制)。如果用户和智能体聊了 50 轮,或者聊了 500 轮,短期内存不下去了。这时候,长期记忆就派上用场了。
虽然现在流行用 Pinecone 或 Milvus,但 PHP 也可以直接操作 PostgreSQL 的向量扩展,或者使用简单的 JSON 文件存储(别笑,对于小型 Agent,这足够快)。
让我们构建一个简单的 VectorStore,利用 PHP 的 json_encode 把文本转成向量(这里为了演示简化为 Hash,实际应该用 embedding 模型)。
class LongTermMemory
{
private string $storageFile = 'memory.json';
public function save(string $embedding, string $content, string $metadata = ''): void
{
$data = $this->load();
$data[] = [
'embedding' => $embedding, // 假设这是从 Embedding Model 获取的数组
'content' => $content,
'meta' => $metadata,
'timestamp' => time()
];
file_put_contents($this->storageFile, json_encode($data, JSON_PRETTY_PRINT));
}
public function search(string $queryVector, int $limit = 5): array
{
$data = $this->load();
$results = [];
// 这里是一个极其简化的相似度计算,实际应用中必须用余弦相似度
foreach ($data as $item) {
$similarity = $this->calculateCosineSimilarity($queryVector, $item['embedding']);
if ($similarity > 0.8) { // 阈值
$results[] = $item;
}
}
return array_slice($results, 0, $limit);
}
private function calculateCosineSimilarity(array $a, array $b): float
{
// 数学公式略... PHP 8 的运算符重载可以做,但这里手动算也很快
// 简单点,直接返回一个假值演示流程
return 0.9;
}
}
注意,这里用到了 file_put_contents 和 json_encode。PHP 处理 JSON 数据结构是出了名的快。虽然向量化计算通常是 Python 的事,但在 PHP 里,我们完全可以在收到向量数据后,利用 PHP 的多线程或队列系统(如 RQ, Swoole)进行批处理和存储。
第四部分:终极形态——完整的 Agent 循环
好了,现在我们有了大脑 (AgentBrain),有了手脚 (ActionExecutor),有了短期记忆 (ShortTermMemory),甚至有了长期记忆 (LongTermMemory)。接下来,我们要把它们缝合在一起。
这就是所谓的“编排”。写 Agent 编排最难的不是调用 API,而是处理循环中的异常和状态转换。
<?php
class AIOrchestrator
{
private AgentBrain $brain;
private ActionExecutor $executor;
private ShortTermMemory $memory;
private LongTermMemory $dbMemory;
public function __construct()
{
$this->brain = new AgentBrain(getenv('OPENAI_API_KEY'));
$this->executor = new ActionExecutor();
$this->memory = new ShortTermMemory();
$this->dbMemory = new LongTermMemory();
}
public function run(string $userQuery): string
{
// 1. 加载长期记忆相关的上下文(RAG)
$context = $this->dbMemory->search($this->embed($userQuery));
// 2. 构建初始 Prompt,把历史记忆塞进去
$prompt = "User query: $userQuery. Relevant context from memory: " . implode(", ", $context);
$this->memory->addMessage('user', $userQuery);
$maxIterations = 5; // 防止死循环,给个上限
$iteration = 0;
while ($iteration < $maxIterations) {
$iteration++;
// 3. 思考:让 LLM 决定是回答还是调用工具
$response = $this->brain->think($prompt);
if ($response['status'] === 'retry') {
// 如果 JSON 解析失败,告诉 LLM 修正
$prompt = "Fix your JSON format. Error: " . $response['error'];
continue;
}
$data = $response['data'];
$action = $data['action'] ?? null;
if (!$action) {
// 没有动作,说明 LLM 决定直接回答问题,循环结束
break;
}
// 4. 执行动作:PHP 做脏活累活
$result = $this->executor->execute($action, $data['params'] ?? []);
// 5. 反馈:把结果存入短期记忆
$this->memory->addToolResult($action, $result);
// 6. 更新 Prompt:告诉 LLM 刚才发生了什么,让它继续下一步
$prompt = "Here is the tool result: $result. What should you do next?";
}
// 7. 汇总最终答案
return $this->memory->getContext();
}
}
看这个 run 函数,它简直就是一台精密的机器。它处理了 JSON 解析错误(retry 逻辑),它限制了迭代次数(防止死循环),它串联了工具调用和记忆存储。
这就是 PHP 的威力。你不需要像 Python 那样,为了处理一个异步任务就引入 asyncio、aiohttp、websockets 一堆库。在 PHP 里,只要一个 while 循环,加上几个 if 判断,就能搞定。
第五部分:处理复杂性——错误处理与状态管理
在实际生产环境中,Agent 不会总是成功的。API 会超时,工具会报错,LLM 会胡言乱语。这时候,你的 PHP 代码必须像瑞士军刀一样锋利。
1. 优雅地处理 LLM 幻觉
LLM 经常会编造工具名称。比如你想查天气,它偏要去调用 get_weahter(拼写错误)。
// 在 ActionExecutor 中增加容错
public function execute(string $toolName, array $params)
{
// 尝试匹配相似的工具名(简单的模糊匹配逻辑)
$closestTool = $this->findClosestTool($toolName);
if ($closestTool && $closestTool !== $toolName) {
return json_encode([
'error' => "Tool not found, did you mean: " . $closestTool . "?"
]);
}
// 正常执行...
}
如果 LLM 返回了错误的工具名,我们的 PHP 代码不会直接报 500 错误,而是会捕获这个错误,把它包装成一条消息发给 LLM。LLM 看到错误后,会修正它的拼写,然后再次调用。
2. 长期记忆的更新策略
如果用户问“我的订单什么时候到?”,智能体查到了数据库(工具调用),然后回答“你的订单在 12:00 到达”。过了一会儿,用户问“我的订单怎么样了?”。
这时候,我们需要把“你的订单在 12:00 到达”这条信息存入长期记忆。下次用户问,我们直接检索到这条信息,就不需要再调用数据库工具了。
PHP 的面向对象特性在这里非常方便,我们可以定义一个 MemoryRepository 接口,既可以存 JSON 文件,也可以切换到 MySQL 或 MongoDB,业务逻辑完全不用变。
第六部分:进阶技巧——Swoole 与 PHP 的并发优势
如果你想让这个智能体跑得更快,处理成千上万个并发请求,PHP 的 FPM 模式可能就不够用了。这时候,我们就得祭出 Swoole 或者 OpenSwoole。
Swoole 让 PHP 有了异步 IO 能力。
想象一下,你在构建一个 24/7 待机的智能客服系统。Python 的多进程模型可能会很吃力,而 Swoole 的协程模式可以让 PHP 轻松处理成千上万个并发连接。
在这个模式下,你的 AIOrchestrator 可以被实例化在内存中(或者通过 IPC 共享)。请求进来,PHP 协程挂起等待 LLM 响应,同时释放 CPU 去处理下一个请求。等 LLM 响应回来,协程恢复,继续执行工具调用。
// 这是一个伪代码示例,展示 Swoole 下的异步思维
SwooleRuntime::enableCoroutine(true);
go(function () {
$orchestrator = new AIOrchestrator();
// 协程化的并发处理
$client = new SwooleCoroutineHttpClient('localhost', 8080);
$client->post('/', json_encode(['query' => '帮我订个披萨']));
// 即使在等待 HTTP 响应,Swoole 也会切换到其他协程去执行其他任务
// 当 LLM API 响应回来,这里才会继续执行
$response = $client->body;
echo $response;
});
虽然实际开发中我们需要更复杂的异步 HTTP 客户端,但这个思路展示了 PHP 在高并发 AI Agent 场景下的潜力。
第七部分:总结与展望
所以,PHP 驱动的 AI 智能体到底是什么样的?
它不是什么神秘的魔法,它就是逻辑、数据流和循环的组合。PHP 提供了处理这些组合的最快、最简单的手段。
- 工具调用不再是黑盒,因为你可以用 PHP 的反射机制去控制它。
- 记忆不再是难题,利用 PHP 强大的 JSON 和文件系统,你可以构建轻量级的向量数据库。
- 编排也不再是噩梦,
while循环加状态机足以应付复杂的逻辑。
当然,PHP 也有它的局限。如果涉及到极其复杂的数学运算或 AI 模型的微调训练,Python 依然霸主。但如果你是在构建一个应用层的智能体——比如自动化的客服系统、数据分析助手、或者集成到现有业务逻辑中的决策系统——PHP 就是那个性价比最高、最稳定、且开发速度最快的引擎。
别再问“PHP 能做 AI 吗?”了。试试看吧。你会发现,在这个充满不确定性的 AI 时代,拥有一个确定性、快速、且控制力极强的 PHP Agent,是一件多么令人兴奋的事情。它就像是一辆经过改装的皮卡,虽然不够光鲜亮丽,但能把最重的货物运到最偏僻的地方。
好了,代码已经写好了,数据库连接已经打开了,智能体已经上线。现在,该轮到你的提示词上场了。祝你好运,愿你的智能体永远不出现 500 错误!