首页 / 文章 / 基于PHP、cPanel和Gemini Flash每月零成本构建生产级AI智能体
← 返回
IT技术

基于PHP、cPanel和Gemini Flash每月零成本构建生产级AI智能体

✍️ zhirenhun 📅 2026/8/16 👁 207 阅读 ⏱ 47 分钟
基于PHP、cPanel和Gemini Flash每月零成本构建生产级AI智能体

在本教程中,你将构建一个实用的AI智能体,它能够接收用户提示,判断是否需要使用工具,在PHP中执行该工具,将对话历史存储在MySQL中,并持续推理直到生成最终答案。

目标不是构建一个炫酷的演示。目标是展示AI智能体如何在一个对许多开发者而言现实可行的技术栈上工作:PHP用于请求处理,MySQL用于持久化,Gemini Flash用于推理和函数调用,cPanel用于在标准共享主机上部署。

阅读完这篇文章,你将理解:

目录

先决条件

开始之前,你应该具备:

你不需要单独的应用服务器。只要你的账户支持PHP、cURL、MySQL和出站HTTPS请求,PHP文件就可以在普通的共享主机上运行。

在典型的cPanel设置中,项目将存储在public_html下的一个文件夹内。

架构概述

系统有五个主要部分:

  1. 一个公共端点接收用户请求。

  2. 智能体循环将对话发送给Gemini Flash。

  3. 工具注册表告诉Gemini哪些函数可用。

  4. PHP工具执行操作,例如保存笔记、搜索网页或发送电子邮件。

  5. MySQL存储对话,以便智能体能够跨请求继续。

流程很直接。用户向PHP端点发送消息。端点将消息和会话ID传递给智能体。智能体加载之前的消息,并将对话连同可用工具一起发送给Gemini。然后Gemini决定要做什么。

如果请求可以直接回答,Gemini会返回文本。如果需要执行操作,Gemini会返回一个包含工具名称及其参数的函数调用。

PHP接收函数调用,运行匹配的工具,并将结果添加到对话中。该结果被发送回Gemini,Gemini随后可以调用另一个工具或返回最终答案。

正是这个循环使应用程序成为智能体,而非基本的聊天机器人。

项目结构

public_html中创建一个名为agent的文件夹:

/public_html/agent/
├── index.php
├── agent.php
├── gemini.php
├── db.php
├── memory.php
├── tool_registry.php
├── tools/
│   ├── save_note.php
│   ├── search_web.php
│   ├── send_email.php
│   └── .htaccess
└── .htaccess

每个文件都有一个主要职责:

将文件分开使项目更易于维护。你可以在不更改应用程序其他部分的情况下添加新工具。

将此添加到 tools/.htaccess

Deny from all

tools文件夹不应通过公共URL访问。这些文件可以写入数据库、发出外部请求或发送电子邮件。

设置MySQL数据库

应用程序需要一个表来存储对话历史,另一个表来存储保存的笔记。

在phpMyAdmin中运行此SQL:

CREATE DATABASE IF NOT EXISTS ai_agent;

USE ai_agent;

CREATE TABLE agent_memory (
    id INT AUTO_INCREMENT PRIMARY KEY,
    session_id VARCHAR(64) NOT NULL,
    role VARCHAR(20) NOT NULL,
    content TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_session_id (session_id)
);

CREATE TABLE agent_notes (
    id INT AUTO_INCREMENT PRIMARY KEY,
    session_id VARCHAR(64) NOT NULL,
    note TEXT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

agent_memory 表存储构成每次对话的消息。session_id 列用于区分不同的对话,而 role 列则标识消息来自用户、模型还是函数。

agent_notes 表存储用户特意要求代理记住的信息。将笔记与对话历史分开存储,可以更轻松地检索并将其用作应用程序数据。

如果 cPanel 为您的数据库名称添加了账户前缀,请在 db.php 中使用完整的名称。例如,ai_agent 可能会变为 account_ai_agent

将 PHP 连接到 MySQL

创建 db.php

 PDO::ERRMODE_EXCEPTION,
                PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            ]
        );
    }

    return $pdo;
}

该函数使用PDO连接MySQL。静态变量确保后续请求重用同一连接,而不是每次代理访问数据库时都打开新连接。

PDO::ATTR_ERRMODE 使数据库故障抛出异常。这使得错误更容易被检测和处理。

ai_agentdb_userdb_password 替换为cPanel中实际的数据库信息。utf8mb4 字符集允许数据库存储各种字符,包括表情符号和非英文文本。

从PHP调用Gemini Flash

Gemini支持函数调用。它可以决定需要某个工具,并返回工具名称和参数。它本身不执行PHP函数。您的应用程序负责验证请求并运行工具。

例如,Gemini可能会返回:

{
  "note": "Our launch is on 1 September 2026"
}

创建 gemini.php

 $contents
    ];

    if (!empty($tools)) {
        $payload["tools"] = [
            [
                "functionDeclarations" => $tools
            ]
        ];
    }

    $ch = curl_init($url);

    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POST => true,
        CURLOPT_HTTPHEADER => [
            "Content-Type: application/json"
        ],
        CURLOPT_POSTFIELDS => json_encode($payload),
        CURLOPT_TIMEOUT => 30
    ]);

    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

    if ($response === false) {
        $error = curl_error($ch);
        curl_close($ch);
        throw new RuntimeException("Gemini request failed: " . $error);
    }

    curl_close($ch);

    if ($httpCode !== 200) {
        throw new RuntimeException("Gemini API error: " . $response);
    }

    $decoded = json_decode($response, true);

    if (!is_array($decoded)) {
        throw new RuntimeException("Gemini returned invalid JSON.");
    }

    return $decoded;
}

function parse_gemini_response(array $response): array
{
    $part = $response["candidates"][0]["content"]["parts"][0] ?? [];

    if (isset($part["functionCall"])) {
        return [
            "type" => "function_call",
            "name" => $part["functionCall"]["name"],
            "args" => $part["functionCall"]["args"] ?? []
        ];
    }

    return [
        "type" => "text",
        "text" => $part["text"] ?? ""
    ];
}

gemini_request 函数将对话和工具定义发送到 Gemini。解析器将 Gemini 的响应转换为文本响应或函数调用。

然后,代理循环可以做出一个简单的决定:

对于实际部署,请尽可能将 API 密钥存储在公共 Web 目录之外。

定义工具注册表

工具注册表告诉 Gemini 存在哪些工具以及它们需要什么参数。

创建 tool_registry.php

 "save_note",
            "description" => "Save an important note to the database.",
            "parameters" => [
                "type" => "object",
                "properties" => [
                    "note" => [
                        "type" => "string"
                    ]
                ],
                "required" => ["note"]
            ]
        ],
        [
            "name" => "search_web",
            "description" => "Search the web for current information.",
            "parameters" => [
                "type" => "object",
                "properties" => [
                    "query" => [
                        "type" => "string"
                    ]
                ],
                "required" => ["query"]
            ]
        ],
        [
            "name" => "send_email",
            "description" => "Send an email when the user explicitly asks.",
            "parameters" => [
                "type" => "object",
                "properties" => [
                    "to" => [
                        "type" => "string"
                    ],
                    "subject" => [
                        "type" => "string"
                    ],
                    "body" => [
                        "type" => "string"
                    ]
                ],
                "required" => ["to", "subject", "body"]
            ]
        ]
    ];
}

描述帮助Gemini决定何时使用工具。参数描述Gemini应提供的值。

注册表不会取代服务器端验证。每个PHP工具在执行操作前仍必须检查自己的参数。

构建工具

每个工具应:

  1. 从Gemini读取参数。

  2. 验证输入。

  3. 执行操作。

  4. 返回JSON结果。

保存笔记工具

创建tools/save_note.php

 false,
            "message" => "Empty note"
        ]);
    }

    $stmt = db()->prepare(
        "INSERT INTO agent_notes (session_id, note)
         VALUES (:session_id, :note)"
    );

    $stmt->execute([
        ":session_id" => $sessionId,
        ":note" => $note
    ]);

    return json_encode([
        "success" => true,
        "message" => "Note saved"
    ]);
}

笔记在保存前会被修剪和检查。预编译语句可防止SQL注入并安全地处理输入。

搜索Web工具

创建 tools/search_web.php

 false,
            "message" => "Search query is empty"
        ]);
    }

    $url = "https://api.example.com/search?q=" . urlencode($query);

    $ch = curl_init($url);

    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 15
    ]);

    $response = curl_exec($ch);

    if ($response === false) {
        $error = curl_error($ch);
        curl_close($ch);

        return json_encode([
            "success" => false,
            "message" => "Search failed",
            "error" => $error
        ]);
    }

    curl_close($ch);

    return $response;
}

该 URL 是一个占位符。请将其替换为您选择的搜索提供商,并添加任何所需的 API 密钥或认证头。

超时设置可防止缓慢的外部服务使 PHP 请求无限期保持打开状态。

发送电子邮件工具

创建 tools/send_email.php

 false,
            "message" => "Invalid email address"
        ]);
    }

    if ($subject === "" || $body === "") {
        return json_encode([
            "success" => false,
            "message" => "Email subject and body are required"
        ]);
    }

    $headers = "From: agent@yourdomain.com\r\n";
    $headers .= "Content-Type: text/plain; charset=UTF-8\r\n";

    $sent = mail($to, $subject, $body, $headers);

    return json_encode([
        "success" => $sent,
        "message" => $sent ? "Email sent" : "Email failed"
    ]);
}

发送前会检查收件人地址、主题和正文。将From地址替换为属于您域名的地址。

mail()函数在共享主机上可能可用,但对于生产环境应用程序,经过身份验证的电子邮件服务或SMTP提供商通常更可靠。

添加MySQL对话记忆

Gemini不会自动记住之前的API请求。应用程序必须在每次请求之前从MySQL加载对话,并在之后保存更新后的历史记录。

创建memory.php

prepare(
        "SELECT role, content
         FROM agent_memory
         WHERE session_id = :session_id
         ORDER BY id ASC"
    );

    $stmt->execute([
        ":session_id" => $sessionId
    ]);

    $history = [];

    foreach ($stmt->fetchAll() as $row) {
        $history[] = [
            "role" => $row["role"],
            "parts" => [
                [
                    "text" => $row["content"]
                ]
            ]
        ];
    }

    return $history;
}

function save_memory(string $sessionId, array $history): void
{
    $pdo = db();

    $delete = $pdo->prepare(
        "DELETE FROM agent_memory
         WHERE session_id = :session_id"
    );

    $delete->execute([
        ":session_id" => $sessionId
    ]);

    $insert = $pdo->prepare(
        "INSERT INTO agent_memory
         (session_id, role, content)
         VALUES (:session_id, :role, :content)"
    );

    foreach ($history as $turn) {
        $part = $turn["parts"][0] ?? [];

        $text = isset($part["text"])
            ? $part["text"]
            : json_encode($part);

        $insert->execute([
            ":session_id" => $sessionId,
            ":role" => $turn["role"],
            ":content" => $text
        ]);
    }
}

load_memory 检索当前会话的消息,并以Gemini期望的格式重新构建它们。

save_memory 用当前历史记录替换存储的历史记录。这很简单,适合小型教程应用。较大的系统可以仅追加新消息、使用JSON列,或总结较旧的对话以减少数据库和API的使用。

创建智能体循环

智能体循环连接数据库、Gemini、记忆和工具。

创建 agent.php

 save_note_tool($args, $sessionId),
        "search_web" => search_web_tool($args),
        "send_email" => send_email_tool($args),
        default => json_encode([
            "success" => false,
            "message" => "Unknown tool"
        ])
    };
}

function run_agent(
    string $message,
    string $sessionId
): string {
    $history = load_memory($sessionId);

    $history[] = [
        "role" => "user",
        "parts" => [
            [
                "text" => $message
            ]
        ]
    ];

    $tools = tool_definitions();
    $limit = 5;
    $step = 0;

    while ($step < $limit) {
        $step++;

        $response = gemini_request($history, $tools);
        $parsed = parse_gemini_response($response);

        if ($parsed["type"] === "text") {
            $history[] = [
                "role" => "model",
                "parts" => [
                    [
                        "text" => $parsed["text"]
                    ]
                ]
            ];

            save_memory($sessionId, $history);

            return $parsed["text"];
        }

        if ($parsed["type"] === "function_call") {
            $toolName = $parsed["name"];
            $toolArgs = $parsed["args"];
            $result = run_tool($toolName, $toolArgs, $sessionId);

            $history[] = [
                "role" => "model",
                "parts" => [
                    [
                        "functionCall" => [
                            "name" => $toolName,
                            "args" => $toolArgs
                        ]
                    ]
                ]
            ];

            $history[] = [
                "role" => "function",
                "parts" => [
                    [
                        "functionResponse" => [
                            "name" => $toolName,
                            "response" => [
                                "content" => $result
                            ]
                        ]
                    ]
                ]
            ];
        }
    }

    save_memory($sessionId, $history);

    return "I could not complete the task within the allowed number of steps.";
}

run_tool 函数会将请求的工具路由到正确的 PHP 函数。默认情况下,它会安全地处理意外工具名称。

run_agent 函数首先加载现有历史记录并添加新的用户消息,然后将对话发送给 Gemini。

如果 Gemini 返回文本,则保存响应并将其返回给用户。

如果 Gemini 返回函数调用,PHP 将执行该工具。函数调用及其结果都会在下一轮循环迭代之前添加到历史记录中。

五步限制可防止模型在没有完成的情况下重复调用工具。您可以根据应用程序的需求调整该限制。

公开公共 API 端点

创建 index.php

 "Invalid JSON body"
    ]);

    exit;
}

$message = trim($input["message"] ?? "");
$sessionId = trim($input["session_id"] ?? "");

if ($message === "" || $sessionId === "") {
    http_response_code(400);

    echo json_encode([
        "error" => "message and session_id are required"
    ]);

    exit;
}

try {
    $reply = run_agent($message, $sessionId);

    echo json_encode([
        "reply" => $reply,
        "session_id" => $sessionId
    ]);
} catch (Throwable $e) {
    http_response_code(500);

    echo json_encode([
        "error" => $e->getMessage()
    ]);
}

该端点期望一个包含消息和会话ID的JSON请求:

{
  "message": "Save a note that our launch is on 1 September 2026",
  "session_id": "demo123"
}

前端应在同一会话中复用相同的会话ID来发送消息。新的会话ID会创建独立的会话。

在开发过程中,返回异常信息有助于调试。在生产环境中,应在内部记录详细的错误日志,并向用户返回通用的错误消息。

在cPanel上部署

请按照以下步骤操作:

  1. 将项目上传到 /public_html/agent/

  2. 在cPanel中创建数据库和用户。

  3. 授予该用户访问数据库的权限。

  4. 在phpMyAdmin中运行SQL。

  5. 更新 db.php 中的凭据。

  6. gemini.php 中添加Gemini API密钥。

  7. 选择PHP 8.1或更高版本。

  8. 启用cURL扩展。

  9. .htaccess 文件添加到 tools 文件夹。

此后,端点应可通过以下地址访问:

https://yourdomain.com/agent/index.php

测试智能体

保存一条笔记:

curl -X POST https://yourdomain.com/agent/index.php \
  -H "Content-Type: application/json" \
  -d '{"message":"Save a note that our launch is on 1 September 2026","session_id":"demo123"}'

代理应该调用save_note,将笔记存储在MySQL中,并返回确认。

您可以通过发送具有相同会话ID的另一个请求来测试记忆:

curl -X POST https://yourdomain.com/agent/index.php \
  -H "Content-Type: application/json" \
  -d '{"message":"What note did I save earlier?","session_id":"demo123"}'

代理应从agent_memory加载之前的对话并用它来回答。

如果出现问题,请检查数据库凭据、数据库表、Gemini API密钥、PHP版本、cURL扩展和服务器错误日志。还要确保搜索工具不再指向占位符API URL。

生产环境加固思路

在允许真实用户访问应用程序之前,请考虑添加:

身份验证

要求提供API令牌或其他身份验证方法。否则,任何发现该端点的人都可能使用您的工具和Gemini账户。

速率限制

按会话ID、IP地址或已验证用户限制请求,以防止滥用和意外使用。

工具日志记录

存储会话ID、工具名称、参数、结果和执行时间。这有助于您调查意外行为。

更强的验证

验证每个工具参数。检查电子邮件地址,拒绝空值,限制字符串长度,并验证数据库标识符。

更好的内存管理

长时间的对话会使API请求变大且效率降低。考虑保留最近的消息、总结较旧的消息,或单独存储结构化的工具调用。

敏感操作的确认

对于发送电子邮件等操作,在执行工具前请用户确认。提示指令很有用,但应用程序也应处理确认。

凭据保护

不要将API密钥和数据库密码存储在公共仓库中。尽可能将敏感配置放在公共Web目录之外。

结论

你不需要复杂的云技术栈来构建实用的AI代理。

借助PHP、MySQL、Gemini Flash和cPanel,你可以创建一个能够推理、调用工具、存储记忆,并运行在许多开发者已经熟悉的基础设施上的系统。

该架构围绕一个简单的流程构建:

  1. 端点接收用户的请求。

  2. MySQL提供之前的对话。

  3. Gemini决定是否需要工具。

  4. PHP执行工具。

  5. 结果发送回Gemini。

  6. Gemini生成最终答案。

  7. 更新后的对话被保存。

这为您在标准共享主机上构建基于代理的应用程序提供了实用基础。

——

🧑‍💻

zhirenhun

一个热爱技术的程序员,喜欢分享前沿AI知识和开发经验。

← 上一篇
[开发日志][Python] 使用Gemini 3.7 Flash从照片和片段制作短视频:ReelCraft
下一篇 →
AI徽章衡量的并非你所想

📌 相关推荐

GraphRAG 是推理问题,而非数据库问题
2026/8/30
构建市场时光机:使用 Python 和 WebSocket 重放交易会话
2026/8/30
如何自行基准测试LLM推理:值得信赖的数字设计标准
2026/8/30
← 返回文章列表