PHP 驱动的 AI 智能体(Agents)编排:利用 PHP 处理复杂的工具调用逻辑与长短期记忆存储

大家好,把手机静音。今天我们不聊那些“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_contentsjson_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 那样,为了处理一个异步任务就引入 asyncioaiohttpwebsockets 一堆库。在 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 错误!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注