本文目录导读:

针对PHP项目私有化大模型在内网环境下的对接调用,核心在于解决网络隔离、API兼容性、性能与安全这三个问题,以下是具体的实现方案和步骤:
核心架构选择(推荐方案)
由于PHP本身是同步阻塞模型,直接通过HTTP调用大模型API(如OpenAI兼容接口)在长耗时推理场景下会阻塞进程,推荐采用反向代理 + 异步任务队列的架构:
[PHP应用] --> [任务队列/异步处理] --> [内网大模型Server] --> [处理结果回调]
^
| (或者)
[PHP应用] --> [内网代理网关] --> [大模型Server]
具体实现步骤
部署内网大模型服务
假设你已内网部署了模型(如通过llama.cpp、vLLM、Ollama、FastChat等),暴露一个OpenAI兼容的HTTP API接口:
# 示例:使用Ollama
curl http://192.168.1.100:11434/api/generate -d '{
"model": "qwen2.5:7b",
"prompt": "你好",
"stream": false
}'
PHP端对接方式
方案A:直接cURL调用(适合轻量、非流式、短响应)
<?php
function callLLM(string $prompt): string {
$url = 'http://192.168.1.100:11434/api/generate';
$data = [
'model' => 'qwen2.5:7b',
'prompt' => $prompt,
'stream' => false,
'options' => [
'num_predict' => 1024,
]
];
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30, // 大模型推理可能超时
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_NOSIGNAL => true, // 防止PHP进程被信号中断
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
throw new Exception("LLM API error: HTTP $httpCode");
}
$result = json_decode($response, true);
return $result['response'] ?? '';
}
注意事项:
CURLOPT_TIMEOUT要设置足够大(如120秒),避免推理未完成被切断。- 如果模型返回速度慢,PHP-FPM进程会被长时间占用,需调整
pm.max_children。
方案B:使用GuzzleHttp客户端(推荐)
composer require guzzlehttp/guzzle
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
$client = new Client([
'base_uri' => 'http://192.168.1.100:11434',
'timeout' => 120.0,
'connect_timeout' => 5.0,
]);
try {
$response = $client->post('/api/generate', [
'json' => [
'model' => 'qwen2.5:7b',
'prompt' => '你好',
'stream' => false
]
]);
$body = json_decode($response->getBody(), true);
echo $body['response'];
} catch (RequestException $e) {
// 处理超时或连接错误
}
处理流式响应(SSE)
如果需要像ChatGPT那样逐字返回,有两种实现方式:
方式A:PHP-FPM + 流式输出(不推荐) 利用PHP的输出缓冲,在每个token到达时flush:
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
$ch = curl_init('http://192.168.1.100:11434/api/generate');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'model' => 'qwen2.5:7b',
'prompt' => '讲个故事',
'stream' => true
]));
curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch, $chunk) {
// 解析SSE数据行
$lines = explode("\n", $chunk);
foreach ($lines as $line) {
if (str_starts_with($line, 'data: ')) {
$data = json_decode(substr($line, 6), true);
echo "data: " . json_encode(['token' => $data['response']]) . "\n\n";
ob_flush();
flush();
}
}
return strlen($chunk);
});
curl_exec($ch);
缺点:PHP-FPM单个进程会长时间占用,并发能力差。
方式B:使用Swoole/Workerman(生产推荐) 利用常驻内存的PHP框架处理SSE:
// 使用Swoole HTTP Server
$http = new Swoole\Http\Server("0.0.0.0", 9501);
$http->on('request', function ($request, $response) {
$response->header('Content-Type', 'text/event-stream');
$response->header('Cache-Control', 'no-cache');
$cli = new Swoole\Coroutine\Http\Client('192.168.1.100', 11434);
$cli->set(['timeout' => -1]);
$cli->post('/api/generate', [
'model' => 'qwen2.5:7b',
'prompt' => $request->get['prompt'] ?? 'hi',
'stream' => true
]);
// 逐行读取流并发送
while (!$cli->eof()) {
$chunk = $cli->recv();
if ($chunk) {
$response->write("data: $chunk\n\n");
}
}
$response->end();
});
$http->start();
内网API网关设计(可选)
如果需要统一管理多个模型或添加认证,可以在PHP和大模型之间加一层Nginx代理:
server {
listen 80;
server_name llm.internal;
location /v1/ {
proxy_pass http://192.168.1.100:11434;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_buffering off; # 流式必须关闭缓冲
proxy_cache off;
proxy_read_timeout 300s;
}
# 添加API Key验证
location /v1/chat/completions {
if ($http_authorization != "Bearer internal-key-xxx") {
return 401;
}
proxy_pass http://192.168.1.200:8000; # 指向另一个模型服务
}
}
性能优化建议
| 问题 | 解决方案 |
|---|---|
| PHP-FPM阻塞 | 使用Swoole/Workerman + 协程处理流式请求 |
| 请求超时 | 设置CURLOPT_TIMEOUT为120秒以上,Nginx proxy_read_timeout同步调整 |
| 并发低 | 增加PHP-FPM pm.max_children,或使用消息队列异步处理 |
| 内存不足 | 大模型推理请使用C++/Python服务,PHP只负责转发 |
| 流式卡顿 | 关闭Nginx、Apache的缓冲:proxy_buffering off; output_buffering = Off |
错误处理与容错
// 重试机制
$maxRetries = 3;
$attempt = 0;
while ($attempt < $maxRetries) {
try {
$result = callLLM($prompt);
break;
} catch (\Exception $e) {
$attempt++;
if ($attempt >= $maxRetries) {
throw $e;
}
usleep(1000000 * $attempt); // 指数退避
}
}
安全注意事项
- 内网隔离:确保模型API只监听内网IP(
0.0.1或168.x.x),不暴露到公网。 - 输入过滤:严格过滤用户输入的prompt,防止SQL注入或XSS(虽然模型一般不会执行,但结果可能反射到页面)。
- 速率限制:在Nginx或PHP端限制单个IP的调用频率,防止滥用。
- 日志审计:记录每次调用的用户、prompt、响应长度,内网环境也应有审计。
完整代码示例(Guzzle + 异步队列)
// 使用Redis队列异步处理
public function handleLongTask(string $prompt, string $callbackUrl) {
// 1. 放入队列
Redis::lpush('llm:queue', json_encode([
'prompt' => $prompt,
'callback' => $callbackUrl
]));
// 2. 返回任务ID,前端轮询结果
return ['task_id' => uniqid()];
}
// 单独的工作进程(CLI模式运行)
while (true) {
$job = Redis::brpop('llm:queue', 10);
if ($job) {
$data = json_decode($job[1], true);
try {
$result = callLLM($data['prompt']);
// 回调通知PHP应用
file_get_contents($data['callback'] . '?result=' . urlencode($result));
} catch (Exception $e) {
// 错误处理
}
}
}
- 简单场景:直接用
curl+timeout设置即可。 - 流式交互:推荐升级到Swoole/Workerman,或使用前端直接连大模型API(如果安全允许)。
- 高并发:必须使用消息队列解耦PHP-FPM和后端模型。
- 生产环境:内网Nginx反代 + 限流 + 日志 + 健康检查。
根据你的实际场景(实时聊天、文档分析、代码生成),选择最合适的架构即可,如果需要进一步的代码调试,可以提供具体的模型地址和响应示例。