当前位置:首页>文章>使用指南>PHP 项目调用大模型 API 全流程实战(适配 OpenAI/国内大模型)

PHP 项目调用大模型 API 全流程实战(适配 OpenAI/国内大模型)

文本是《AI咨询(共38篇)》专题的第 38 篇。阅读本文前,建议先阅读前面的文章:

一、实战前置:理清核心逻辑与准备工作

1.1 核心逻辑

PHP 调用大模型 API 的核心逻辑与 Java 一致:通过 HTTP 请求与大模型服务端通信,按照 API 规范构造请求参数,接收并解析 JSON 响应结果。不同厂商 API 仅在请求格式、认证方式上略有差异,核心流程通用。

PHP 项目调用大模型 API 全流程实战(适配 OpenAI/国内大模型)

1.2 环境与工具准备

  • PHP 版本:推荐 7.4 及以上(兼容主流扩展,支持强类型提示);

  • 依赖管理:Composer(PHP 生态主流依赖管理工具,简化扩展安装);

  • HTTP 客户端:Guzzle HTTP(高效稳定,支持异步请求,是 PHP 生产环境的首选 HTTP 工具);

  • JSON 处理:PHP 内置 json_encode()/json_decode()(无需额外扩展,满足序列化/反序列化需求);

  • API 密钥:提前在对应大模型厂商官网申请(OpenAI、一步 AI、百度文心一言等);

  • 开发工具:PhpStorm(或 VS Code + PHP 插件),搭配 Postman 辅助接口调试。

1.3 核心依赖配置(composer.json)

通过 Composer 引入 Guzzle HTTP 扩展,同时指定 PHP 版本约束,避免兼容性问题。

{
    "name": "com/llm/demo",
    "description": "PHP 调用大模型 API 实战 Demo",
    "type": "project",
    "require": {
        "php": ">=7.4",
        "guzzlehttp/guzzle": "^7.8",  // HTTP 客户端核心依赖
        "monolog/monolog": "^2.9"     // 日志框架,便于调试与问题排查
    },
    "autoload": {
        "psr-4": {
            "LlmDemo\\": "src/"
        }
    }
}

配置完成后,执行 composer install 安装依赖,生成 vendor 目录和自动加载文件。

二、核心实现:从数据模型到 API 调用工具类

PHP 无原生数据模型注解(对应 Java 的 Lombok/Jackson),我们通过标准类 + 魔术方法/显式赋值实现数据模型,通过关联数组构造请求参数(兼容 JSON 序列化),核心工具类封装 HTTP 请求、认证、异常处理等通用逻辑。

2.1 数据模型定义

以 OpenAI Chat Completions API 为例,复刻 Java 中的 4 个核心模型,保证请求/响应格式与 API 严格对应。

2.1.1 消息体模型(Message.php)

对应对话的核心单元,包含 role(角色)和 content(内容)两个字段,角色说明与 Java 一致(system/user/assistant)。

<?php
/**
 * 命名空间:对应 src/Model 目录,符合 PSR-4 自动加载规范
 */
namespace LlmDemo\Model;

/**
 * 对话消息体模型
 * 角色说明:
 * - system:系统指令,用于定义大模型的行为
 * - user:用户输入的问题/指令
 * - assistant:大模型生成的响应内容
 */
class Message
{
    /**
     * 消息角色
     * @var string
     */
    public $role;

    /**
     * 消息内容
     * @var string
     */
    public $content;

    /**
     * 构造方法:快速初始化消息对象
     * @param string $role
     * @param string $content
     */
    public function __construct(string $role, string $content)
    {
        $this->role = $role;
        $this->content = $content;
    }

    /**
     * 转换为关联数组:便于后续 JSON 序列化
     * @return array
     */
    public function toArray(): array
    {
        return [
            'role' => $this->role,
            'content' => $this->content
        ];
    }
}

2.1.2 请求体模型(ChatRequest.php)

封装调用大模型所需的全部参数,核心参数与 Java 一致,提供 toArray() 方法用于构造 JSON 请求体。

<?php
namespace LlmDemo\Model;

use InvalidArgumentException;

/**
 * 大模型 API 请求体模型
 */
class ChatRequest
{
    /**
     * 大模型名称(如 gpt-3.5-turbo、gpt-4)
     * @var string
     */
    public $model;

    /**
     * 对话消息列表(包含历史对话上下文)
     * @var Message[]
     */
    public $messages;

    /**
     * 温度:值越高,生成内容越随机;值越低,生成内容越严谨(0~2)
     * @var float
     */
    public $temperature;

    /**
     * 最大生成 Token 数:控制响应内容的长度
     * @var int
     */
    public $max_tokens;

    /**
     * 构造方法:初始化请求参数
     * @param string $model
     * @param Message[] $messages
     * @param float $temperature
     * @param int $max_tokens
     */
    public function __construct(string $model, array $messages, float $temperature, int $max_tokens)
    {
        // 校验消息列表是否为合法的 Message 实例
        foreach ($messages as $message) {
            if (!($message instanceof Message)) {
                throw new InvalidArgumentException("消息列表必须包含 Message 实例");
            }
        }

        $this->model = $model;
        $this->messages = $messages;
        $this->temperature = $temperature;
        $this->max_tokens = $max_tokens;
    }

    /**
     * 转换为关联数组:用于 JSON 序列化
     * @return array
     */
    public function toArray(): array
    {
        // 转换消息列表为关联数组集合
        $messageArrays = [];
        foreach ($this->messages as $message) {
            $messageArrays[] = $message->toArray();
        }

        return [
            'model' => $this->model,
            'messages' => $messageArrays,
            'temperature' => $this->temperature,
            'max_tokens' => $this->max_tokens
        ];
    }
}

2.1.3 Token 用量模型(Usage.php)

用于统计本次调用的 Token 消耗,便于成本控制和额度管理。

<?php
namespace LlmDemo\Model;

/**
 * Token 用量统计模型
 */
class Usage
{
    /**
     * 请求消耗 Token 数
     * @var int
     */
    public $prompt_tokens;

    /**
     * 响应消耗 Token 数
     * @var int
     */
    public $completion_tokens;

    /**
     * 总消耗 Token 数
     * @var int
     */
    public $total_tokens;

    /**
     * 从 JSON 关联数组初始化对象
     * @param array $data
     * @return self
     */
    public static function fromArray(array $data): self
    {
        $usage = new self();
        $usage->prompt_tokens = $data['prompt_tokens'] ?? 0;
        $usage->completion_tokens = $data['completion_tokens'] ?? 0;
        $usage->total_tokens = $data['total_tokens'] ?? 0;
        return $usage;
    }
}

2.1.4 响应体模型(ChatResponse.php)

对应大模型返回的结果,包含对话 ID、生成内容、Token 用量等信息,提供静态方法从 JSON 响应初始化对象。

<?php
namespace LlmDemo\Model;

/**
 * 大模型 API 响应体模型
 */
class ChatResponse
{
    /**
     * 对话唯一 ID
     * @var string
     */
    public $id;

    /**
     * 响应类型
     * @var string
     */
    public $object;

    /**
     * 响应生成时间戳(秒级)
     * @var int
     */
    public $created;

    /**
     * 所用模型名称
     * @var string
     */
    public $model;

    /**
     * 响应内容列表
     * @var ChatResponseChoice[]
     */
    public $choices;

    /**
     * Token 用量统计
     * @var Usage
     */
    public $usage;

    /**
     * 从 JSON 关联数组初始化响应对象
     * @param array $data
     * @return self
     * @throws \InvalidArgumentException
     */
    public static function fromArray(array $data): self
    {
        if (empty($data['choices'])) {
            throw new \InvalidArgumentException("响应内容中无有效 choices 字段");
        }

        $response = new self();
        $response->id = $data['id'] ?? '';
        $response->object = $data['object'] ?? '';
        $response->created = $data['created'] ?? 0;
        $response->model = $data['model'] ?? '';

        // 初始化 choices 列表
        $response->choices = [];
        foreach ($data['choices'] as $choiceData) {
            $response->choices[] = ChatResponseChoice::fromArray($choiceData);
        }

        // 初始化 Token 用量
        $response->usage = Usage::fromArray($data['usage'] ?? []);

        return $response;
    }
}

/**
 * 响应内容详情子模型
 */
class ChatResponseChoice
{
    /**
     * 响应索引
     * @var int
     */
    public $index;

    /**
     * 响应消息体
     * @var Message
     */
    public $message;

    /**
     * 响应结束原因(stop/长度超限等)
     * @var string
     */
    public $finish_reason;

    /**
     * 从 JSON 关联数组初始化对象
     * @param array $data
     * @return self
     */
    public static function fromArray(array $data): self
    {
        $choice = new self();
        $choice->index = $data['index'] ?? 0;
        $choice->finish_reason = $data['finish_reason'] ?? '';

        // 初始化响应消息体
        $messageData = $data['message'] ?? [];
        $choice->message = new Message(
            $messageData['role'] ?? '',
            $messageData['content'] ?? ''
        );

        return $choice;
    }
}

2.2 核心调用工具类封装

工具类 OpenAIClient.php 封装 Guzzle HTTP 客户端配置、请求构造、认证处理、响应解析和异常处理,支持单轮对话和多轮对话,对应 Java 中的核心工具类逻辑。

<?php
namespace LlmDemo\Client;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use LlmDemo\Model\ChatRequest;
use LlmDemo\Model\ChatResponse;
use LlmDemo\Model\Message;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

/**
 * OpenAI 大模型 API 调用工具类
 * 封装通用调用逻辑,支持单轮/多轮对话
 */
class OpenAIClient
{
    /**
     * 日志实例
     * @var Logger
     */
    private $logger;

    /**
     * Guzzle HTTP 客户端
     * @var Client
     */
    private $httpClient;

    /**
     * 大模型 API 密钥
     * @var string
     */
    private $apiKey;

    /**
     * 大模型 API 地址
     * @var string
     */
    private $apiUrl;

    /**
     * 大模型名称
     * @var string
     */
    private $model;

    /**
     * 温度参数
     * @var float
     */
    private $temperature;

    /**
     * 最大 Token 数
     * @var int
     */
    private $maxTokens;

    /**
     * 构造方法:初始化配置与依赖
     * @param string $apiKey API 密钥
     * @param string $apiUrl API 地址
     * @param string $model 模型名称
     * @param float $temperature 温度参数
     * @param int $maxTokens 最大 Token 数
     */
    public function __construct(string $apiKey, string $apiUrl, string $model, float $temperature, int $maxTokens)
    {
        // 初始化日志
        $this->logger = new Logger('llm_api_client');
        $this->logger->pushHandler(new StreamHandler(__DIR__ . '/../../logs/llm_api.log', Logger::INFO));

        // 初始化 Guzzle 客户端:设置超时时间、自动重试
        $this->httpClient = new Client([
            'connect_timeout' => 30,  // 连接超时 30 秒
            'timeout' => 60,          // 读取超时 60 秒
            'allow_redirects' => false,
            'verify' => true          // 生产环境开启 SSL 验证
        ]);

        // 赋值配置参数
        $this->apiKey = $apiKey;
        $this->apiUrl = $apiUrl;
        $this->model = $model;
        $this->temperature = $temperature;
        $this->maxTokens = $maxTokens;
    }

    /**
     * 单轮对话调用(无上下文关联)
     * @param string $userMessage 用户输入内容
     * @return string 大模型响应内容
     * @throws GuzzleException 网络请求异常
     * @throws \Exception 解析/业务异常
     */
    public function singleChat(string $userMessage): string
    {
        // 构造消息列表:仅包含用户输入
        $messages = [
            new Message('user', $userMessage)
        ];

        // 构造请求体
        $chatRequest = new ChatRequest(
            $this->model,
            $messages,
            $this->temperature,
            $this->maxTokens
        );

        // 发送请求并处理响应
        return $this->sendRequest($chatRequest);
    }

    /**
     * 多轮对话调用(支持上下文关联)
     * @param Message[] $historyMessages 历史对话消息列表
     * @return string 大模型响应内容
     * @throws GuzzleException 网络请求异常
     * @throws \Exception 解析/业务异常
     */
    public function multiChat(array $historyMessages): string
    {
        // 构造请求体
        $chatRequest = new ChatRequest(
            $this->model,
            $historyMessages,
            $this->temperature,
            $this->maxTokens
        );

        // 发送请求并处理响应
        return $this->sendRequest($chatRequest);
    }

    /**
     * 发送 HTTP 请求并解析响应
     * @param ChatRequest $chatRequest 请求体实例
     * @return string 大模型核心响应内容
     * @throws GuzzleException 网络请求异常
     * @throws \Exception 解析/业务异常
     */
    private function sendRequest(ChatRequest $chatRequest): string
    {
        try {
            // 1. 构造 HTTP 请求参数
            $requestData = $chatRequest->toArray();
            $jsonPayload = json_encode($requestData, JSON_UNESCAPED_UNICODE);
            if (json_last_error() !== JSON_ERROR_NONE) {
                throw new \Exception("请求数据 JSON 序列化失败:" . json_last_error_msg());
            }

            // 2. 发送 POST 请求
            $response = $this->httpClient->post($this->apiUrl, [
                'headers' => [
                    'Authorization' => 'Bearer ' . $this->apiKey,
                    'Content-Type' => 'application/json',
                    'Accept' => 'application/json'
                ],
                'body' => $jsonPayload
            ]);

            // 3. 处理响应状态码
            $statusCode = $response->getStatusCode();
            if ($statusCode < 200 || $statusCode >= 300) {
                $errorContent = (string) $response->getBody();
                $this->logger->error("API 调用失败,状态码:{$statusCode},错误信息:{$errorContent}");
                throw new \Exception("API 调用失败,状态码:{$statusCode},错误信息:{$errorContent}");
            }

            // 4. 解析响应内容
            $responseContent = (string) $response->getBody();
            $responseData = json_decode($responseContent, true);
            if (json_last_error() !== JSON_ERROR_NONE) {
                throw new \Exception("响应内容 JSON 解析失败:" . json_last_error_msg());
            }

            // 5. 转换为响应对象并提取核心内容
            $chatResponse = ChatResponse::fromArray($responseData);
            $coreResponse = $chatResponse->choices[0]->message->content;

            // 6. 记录 Token 用量日志
            $usage = $chatResponse->usage;
            $this->logger->info(
                "本次调用 Token 用量:请求 {$usage->prompt_tokens} 个,响应 {$usage->completion_tokens} 个,总计 {$usage->total_tokens} 个"
            );

            return $coreResponse;
        } catch (GuzzleException $e) {
            $this->logger->error("HTTP 请求异常:" . $e->getMessage());
            throw $e;
        } catch (\Exception $e) {
            $this->logger->error("业务处理异常:" . $e->getMessage());
            throw $e;
        }
    }
}

2.3 配置文件与入口执行脚本

2.3.1 配置文件(config.php)

避免硬编码,将配置参数独立存储,对应 Java 中的 application.properties

<?php
/**
 * 大模型 API 配置文件
 * 请替换为自己的有效配置
 */
return [
    // OpenAI 配置
    'openai' => [
        'api_key' => 'your-openai-api-key',  // 替换为你的 API 密钥
        'api_url' => 'https://yibuapi.com/v1/chat/completions',
        'model' => 'gpt-3.5-turbo',
        'temperature' => 0.7,
        'max_tokens' => 2048
    ]
];

2.3.2 入口执行脚本(index.php)

对应 Java 中的启动类,初始化客户端并测试单轮/多轮对话功能。

<?php
/**
 * 大模型 API 调用实战 Demo 入口脚本
 */
require __DIR__ . '/vendor/autoload.php';

use LlmDemo\Client\OpenAIClient;
use LlmDemo\Model\Message;

// 1. 加载配置
$config = require __DIR__ . '/config.php';
$llmConfig = $config['openai'];

// 2. 初始化 OpenAI 客户端
$openAIClient = new OpenAIClient(
    $llmConfig['api_key'],
    $llmConfig['api_url'],
    $llmConfig['model'],
    $llmConfig['temperature'],
    $llmConfig['max_tokens']
);

// 3. 测试 1:单轮对话(无上下文)
echo "===== 单轮对话测试 =====\n";
try {
    $singleResponse = $openAIClient->singleChat("请用 PHP 实现单例模式的懒汉式写法,并说明线程安全问题");
    echo "大模型响应:\n{$singleResponse}\n\n";
} catch (Exception $e) {
    echo "单轮对话调用失败:" . $e->getMessage() . "\n\n";
}

// 4. 测试 2:多轮对话(带上下文)
echo "===== 多轮对话测试 =====\n";
try {
    $historyMessages = [];
    // ① 添加系统指令:定义大模型行为
    $historyMessages[] = new Message('system', '你是一名资深 PHP 开发工程师,回答简洁准确,重点突出技术细节');
    // ② 第一轮用户提问
    $historyMessages[] = new Message('user', '请用 PHP 实现多线程下载文件的功能(基于 PCNTL 扩展)');
    $firstResponse = $openAIClient->multiChat($historyMessages);
    echo "第一轮响应:\n{$firstResponse}\n\n";

    // ③ 第二轮追问(基于上下文):优化第一轮代码
    $historyMessages[] = new Message('assistant', $firstResponse);  // 将大模型响应加入上下文
    $historyMessages[] = new Message('user', '如何优化这个代码,提升下载效率并保证线程安全?');
    $secondResponse = $openAIClient->multiChat($historyMessages);
    echo "第二轮响应:\n{$secondResponse}\n\n";
} catch (Exception $e) {
    echo "多轮对话调用失败:" . $e->getMessage() . "\n\n";
}

三、场景适配:国内大模型对接方案

PHP 版本的适配逻辑与 Java 一致,多数国内大模型(一步 AI、百度文心一言、阿里云通义千问)API 采用与 OpenAI 兼容的设计,仅需修改配置文件即可快速适配。

3.1 一步 AI 适配示例(修改 config.php)

<?php
/**
 * 国内大模型(一步 AI GLM-4)配置示例
 */
return [
    // 一步 AI GLM-4 配置
    'openai' => [
        'api_key' => 'your-glm-api-key',  // 替换为你的一步 AI API 密钥
        'api_url' => 'https://yibuapi.com/api/paas/v4/chat/completions',
        'model' => 'glm-4',  // 一步 AI 模型名称
        'temperature' => 0.7,
        'max_tokens' => 2048
    ]
];

3.2 适配说明

  1. 认证方式:国内大模型多采用 Bearer Token 认证,与 OpenAI 一致,无需修改 OpenAIClient 核心代码;

  2. 请求/响应格式:一步 AI 等国内模型与 OpenAI 格式完全兼容,数据模型无需调整;

  3. 特殊场景:若部分厂商 API 字段存在差异,仅需修改对应模型的 toArray()/fromArray() 方法中的字段名即可。

四、生产级优化:避坑指南与性能提升

4.1 安全性优化

  1. 密钥安全:避免将 API 密钥存储在明文配置文件中,推荐使用环境变量getenv('LLM_API_KEY'))、配置中心(如 Nacos PHP 客户端)或加密存储,防止密钥泄露;

  2. 请求校验:对用户输入进行过滤(过滤特殊字符、限制长度),避免恶意注入,同时添加请求频率限制(如基于 Redis 实现限流),防止 API 滥用。

4.2 稳定性优化

  1. 重试机制:针对网络波动、503 等临时错误,给 Guzzle 客户端添加重试中间件(guzzlehttp/guzzle-retry-middleware),设置合理的重试次数(3 次以内)和间隔(指数退避);

  2. 熔断降级:使用 PHP 熔断框架(如 hollodotme/circuit-breaker),当 API 持续异常时触发熔断,返回兜底响应,避免影响整个系统;

  3. 超时控制:根据网络情况调整 Guzzle 超时时间,生产环境建议连接超时 10 秒、读取超时 30 秒,避免请求长时间阻塞。

4.3 性能与成本优化

  1. 上下文管理:多轮对话中裁剪无效历史消息,仅保留关键上下文,减少 Token 消耗和请求体大小;

  2. 批量调用:若 API 支持批量请求,将多个小请求合并为一个,减少 HTTP 请求次数;

  3. 模型选择:非核心场景使用轻量模型(如 gpt-3.5-turbo/glm-3),降低成本并提升响应速度。

4.4 常见坑与解决方案

  1. 401 未授权:检查 API 密钥是否正确,是否包含空格/特殊字符,部分国内模型需在密钥前添加前缀(参考官方文档);

  2. 429 频率超限:添加限流机制,控制调用频率,或联系厂商提升额度;

  3. JSON 解析失败:检查请求体是否符合 API 规范(如字段类型、必填项),PHP 中 json_encode() 需注意中文转义问题(添加 JSON_UNESCAPED_UNICODE 参数);

  4. 国内网络访问问题:对接 OpenAI 等国外模型时,需给 Guzzle 配置合法代理('proxy' => 'http://xxx:xxx'),或选择国内代理服务。

五、总结与扩展

本文完整实现了 PHP 项目调用大模型 API 的全流程,复刻了 Java 版本的核心逻辑,基于 Guzzle HTTP 构建了高可用的调用工具类,支持单轮/多轮对话,且兼容国内大模型适配。

使用指南

Mistral 3系列模型国内直连指南:突破壁垒的API中转方案

2025-12-26 7:56:30

使用指南

GPT-5:国内开发者零门槛接入指南,低价多模态API实战方案

2025-12-5 15:11:55

搜索