news 2026/9/17 3:45:56

PHP接入DeepSeek R1满血版实战:API调用与Function Calling

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PHP接入DeepSeek R1满血版实战:API调用与Function Calling

简介:面向PHP开发者的DeepSeek R1满血版大模型接入DEMO,重点解决在PHP项目中对接大模型接口、缺少可直接运行的参考代码、界面展示不直观等常见问题,适合有一定PHP基础、正在做AI功能集成或毕业设计项目的开发者。该示例采用仿微信聊天PC端界面,将前端聊天页面与PHP后端请求逻辑拆开展示,既能看到界面效果,也能快速定位接口调用过程;配合详细中文注释,从发起请求、携带参数到接收并渲染返回内容,覆盖一个聊天对话闭环。压缩包共30个文件,包含PHP源码、HTML页面、PNG界面截图和2个URL快捷方式;两个URL快捷方式分别指向解压密码与安装说明,整体包体仅5.56MB,结构简洁、下载与传阅都很轻便。代码中多处注解说明对接要点,并配有多个聊天界面截图,便于理解前后端交互流程,二次开发和样式改造成本较低。目前已有218人学习/下载,对于需要快速搭建PHP版DeepSeek对话Demo的开发者而言,是一份可直接上手的实战参考。

1. 先对齐概念:PHP 项目里接入 DeepSeek R1 满血版,到底在做一件什么事

把标题拆开看,真正的技术动作只有两个:一是构造一个 OpenAI 兼容格式的 HTTP POST 请求,把用户输入发给 DeepSeek 的对话补全接口;二是把返回的流式或非流式结果接进 PHP 业务代码。R1 是推理模型,接口层面对应两个约定:模型名写deepseek-reasoner,输出里多一个reasoning_content字段存放思维链。社区说的“满血版”指完整参数的 MoE 大模型,官方 API 拿到的就是完整链路,不需要额外加任何后缀。

适合读这篇的人有两类:现有 PHP 业务系统想加智能问答或 Agent 能力的人,以及刚接触大模型 API、想搞懂请求格式和参数约束的开发者。这篇从开通 API Key 开始,覆盖请求生命周期、流式输出、上下文裁剪、Function Calling,末尾给出可直接改用的函数调用 DEMO。

2. 接入前的准备:API Key、模型标识与请求链路选择

2.1 官方 API 对“满血版 R1”的模型标识是 deepseek-reasoner

拿到 API Key 之后,第一步是确认模型标识。DeepSeek 官方开放平台提供两个模型字符串:deepseek-chat对应非推理的对话模型,deepseek-reasoner对应 R1 推理模型。标题里的“满血版 R1”在官方 API 层面没有任何特殊标识,它就是deepseek-reasoner,不用拼接版本号、参数规模或日期后缀。社区里偶尔看到的“满血版”“联网版”说法,都只是在描述同一模型的不同调用形态,接口地址和鉴权方式完全一致。

有一点要注意:deepseek-reasoner的输出里会带有思维链内容。这个内容在普通 chat 模型里没有,如果你直接把它当成正文返回给用户看,体验会很奇怪。正确做法是在响应 JSON 里读取choices[0].message.content作为最终展示内容,reasoning_content只用于日志或调试。上一轮对话里如果发送了role=assistant且带reasoning_content的消息,下一轮会报参数校验错误,所以存历史时要把这个字段剥掉再回传。

2.2 Composer 依赖最小集:Guzzle 与原生 curl 各解决什么问题

PHP 项目接入 DeepSeek 不需要任何官方 SDK,一个能发 HTTPS 请求的客户端就够了。原生 curl 扩展是最稳妥的选择,几乎所有 PHP 7.4+ 环境都默认开启,不需要额外装包。Guzzle 的价值在于连接池、超时控制、异常处理和请求中间件,适合生产环境里要做重试、日志、多模型切换的项目。

composer require guzzlehttp/guzzle:^7.0

Guzzle 7 要求 PHP 7.2.5 以上,大部分线上环境都满足。如果项目还用 PHP 5.6 或老版本,就放弃 composer 方案,直接走 curl 扩展,后面给的 curl DEMO 不做任何框架依赖,PHP 5.6 也能跑。

提示:网络异常时优先检查 PHP 的 curl 扩展是否启用,命令行执行php -m | grep curl能看到即正常。composer 安装失败也和 openssl 扩展有关,两者缺一不可。

2.3 用一条 curl 命令验证 API Key 有效性

进入写代码之前,先跑通最小链路。在命令行里用 curl 直接打一次接口,确认 Key 有效、账号没有欠费、模型标识没写错。

curl https://api.deepseek.com/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk-xxxx" \ -d '{ "model": "deepseek-reasoner", "messages": [ {"role": "user", "content": "用一句话介绍你自己"} ], "max_tokens": 512 }'

返回 JSON 里能看到idmodelchoices三个字段,表示请求链路已通。401 代表 Key 无效,402 或余额不足提示表示账号欠费,400 通常是 JSON 格式或参数问题。这一步能过滤掉后续代码里 80% 的偶发问题。

3. 写第一个 PHP DEMO:curl 与 Guzzle 双版本对话请求

3.1 PHP 原生 curl 版本:不依赖 composer 的最小对话 DEMO

<?php function callDeepSeek(string $apiKey, array $messages, array $tools = []): array { $url = 'https://api.deepseek.com/chat/completions'; $payload = [ 'model' => 'deepseek-reasoner', 'messages' => $messages, 'max_tokens' => 1024, 'temperature' => 0.6, 'stream' => false, ]; if ($tools) { $payload['tools'] = $tools; } $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE), CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Authorization: Bearer ' . $apiKey, ], CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 60, CURLOPT_CONNECTTIMEOUT => 10, ]); $response = curl_exec($ch); if (curl_errno($ch)) { $error = curl_error($ch); curl_close($ch); throw new RuntimeException('请求失败: ' . $error); } $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $data = json_decode($response, true); if ($httpCode >= 400) { throw new RuntimeException( 'HTTP ' . $httpCode . ': ' . ($data['error']['message'] ?? $response) ); } return $data; } $apiKey = getenv('DEEPSEEK_API_KEY'); $messages = [['role' => 'user', 'content' => 'PHP如何接入大模型,给出三点建议']]; $result = callDeepSeek($apiKey, $messages); echo $result['choices'][0]['message']['content'] . PHP_EOL;

CURLOPT_CONNECTTIMEOUT控制 TCP 建连超时,CURLOPT_TIMEOUT是整次请求的上限。R1 是推理模型,生成速度比 chat 模型慢,60 秒超时对普通问题够用,但涉及长文本生成时建议放宽到 120 秒,否则会出现 HTTP 200 之前就被 PHP 掐断的情况。json_decode后的error字段里会有具体报错原因,生产环境记得把$response原文留到日志。

代码里把 API Key 放在环境变量,不要硬编码在提交里。getenv('DEEPSEEK_API_KEY')取值失败时会传一个空字符串,服务端会返回 401,比在本地报错更容易定位。

3.2 Guzzle 版本:异常处理与响应结构更友好

<?php require 'vendor/autoload.php'; use GuzzleHttp\Client; use GuzzleHttp\Exception\RequestException; function callDeepSeekWithGuzzle(string $apiKey, array $messages): array { $client = new Client([ 'base_uri' => 'https://api.deepseek.com/', 'timeout' => 60, 'connect_timeout' => 10, ]); try { $response = $client->post('chat/completions', [ 'headers' => [ 'Authorization' => 'Bearer ' . $apiKey, 'Content-Type' => 'application/json', ], 'json' => [ 'model' => 'deepseek-reasoner', 'messages' => $messages, 'max_tokens' => 1024, 'temperature' => 0.6, 'stream' => false, ], ]); return json_decode($response->getBody()->getContents(), true); } catch (RequestException $e) { $responseBody = $e->hasResponse() ? $e->getResponse()->getBody()->getContents() : $e->getMessage(); throw new RuntimeException('DeepSeek 调用失败: ' . $responseBody); } }

Guzzle 的json选项会自动做json_encode并设置Content-Type,不用手动拼请求体。异常类型建议捕获RequestException,网络层错误和 HTTP 4xx/5xx 都归到这一类。有响应体时优先取响应体里的error.message,比直接抛网络异常更容易定位。

3.3 R1 模型上三个关键参数:不是照抄都有效

R1 和普通对话模型对参数的容忍度不一样。下面是接入时最容易出问题的三组参数:

参数推荐值对 R1 的影响
max_tokens1024~4096控制新生成 token 上限,不是总量;设置太小,思维链还没结束就被截断
temperature0.5~0.7R1 对 temperature 响应不敏感,0.6 是多数场景均衡点,不需要像 chat 模型那样拉到 1.0+
streamfalse / true决定响应形态;默认 false 一次返回完整 JSON,true 返回 SSE 流

常见误用是把max_tokens当成整个会话的总长度来设。模型实际做的事情是“输入 tokens + 输出 tokens”共享上下文窗口,但 API 参数里的max_tokens只约束输出部分。上下文超限是另一个错误,会在第 4 章单独讲裁剪策略。temperature如果设成 0,R1 反而容易陷入重复输出,建议保守落在 0.5 到 0.7 区间。

4. 流式输出与上下文管理:智能体对话状态怎么保持

4.1 SSE 流式接收:PHP 逐行解析 data: 前缀

智能体场景里用户会等回复,等 20 秒没有反馈的体验是灾难性的。官方接口支持 SSE 流式返回,服务端通过text/event-stream协议逐段推送增量内容,PHP 端用 curl 逐行读取即可。

<?php function streamDeepSeek(string $apiKey, array $messages): void { $ch = curl_init('https://api.deepseek.com/chat/completions'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode([ 'model' => 'deepseek-reasoner', 'messages' => $messages, 'stream' => true, ]), CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Authorization: Bearer ' . $apiKey, ], CURLOPT_WRITEFUNCTION => function ($ch, $chunk) { $lines = preg_split('/\R/', $chunk); foreach ($lines as $line) { $line = trim($line); if ($line === '' || strpos($line, 'data:') !== 0) { continue; } $data = trim(substr($line, 5)); if ($data === '[DONE]') { return strlen($chunk); } $json = json_decode($data, true); if (isset($json['choices'][0]['delta']['content'])) { echo $json['choices'][0]['delta']['content']; flush(); } } return strlen($chunk); }, ]); curl_exec($ch); curl_close($ch); }

CURLOPT_WRITEFUNCTION在 PHP 里扮演数据回调角色,每次收到新片段就执行一次。需要特别解释的是返回值:回调必须返回本次处理的字节数,否则 curl 会认为写入失败而中断请求,这是流式 Demo 最容易漏的细节。preg_split按行切分是因为 SSE 的每个事件以换行分隔,data:之后紧跟的就是增量 JSON。flush()用于刷新 PHP 输出缓冲区,在 Nginx + php-fpm 环境下需要关掉 gzip 和output_buffering才能达到真正的逐字效果。

提示:Web 环境下建议把任务丢给 Swoole 或 Workerman 的异步进程处理,避免一个长连接占满 php-fpm 进程。

4.2 messages 数组的维护规则:多轮对话正确回传的格式

智能体区别于一次性问答的关键在多轮记忆。官方接口不存储会话状态,每一次请求都要由客户端把完整对话历史通过messages参数传回。messages是一个数组,每个元素包含rolecontent两个核心字段。

三种合法的 role 是systemuserassistant。常见的数据构造方式是:

$messages = [ ['role' => 'system', 'content' => '你是企业客服助手,回答要求简洁、准确。'], ['role' => 'user', 'content' => '怎么修改订单地址?'], ['role' => 'assistant', 'content' => '请提供订单号,我帮你查到后指引修改。'], ['role' => 'user', 'content' => '订单号是 20250601A'], ];

有一个隐藏约束:从 API 返回的 assistant 消息里自带reasoning_content字段,这个字段不能原样塞回下一轮请求,否则会返回 400 参数错误。回传前用unset($item['reasoning_content'])剥离。每个角色的消息都应完整保留原始content,不要拼成一大段文本再传,按数组回传才能让模型区分说话人。

4.3 上下文超限与错误码:字段太长、欠费、模型不存在的处理

当会话轮数多了之后会出现一个典型报错:HTTP 400,提示 this model's maximum context length。官方接口的 token 预算总量有限,max_tokens只是输出部分的分配,但请求中messages的累计长度不能超过窗口上限,具体上限以报错信息提示为准。常见的解决方式是维护一个“最近 N 轮”策略:

function trimMessages(array $messages, int $maxRounds = 10): array { $system = []; if ($messages[0]['role'] === 'system') { $system[] = array_shift($messages); } if (count($messages) > $maxRounds * 2) { $messages = array_slice($messages, -($maxRounds * 2)); } return array_merge($system, $messages); }

计算 token 数量最准确的做法是调用 tokenizer 统计,但 PHP 端没有官方包,工程上先用估算:中文按字符数除以 1.6、英文按单词数乘以 1.3,总量超过 80K 时触发裁剪。除 400 以外,429 表示触发频率限制,需要退避重试;401 表示鉴权字段不正确;404 通常意味着模型标识或接口路径写错。日志里记录下 HTTP 状态码、请求 ID、响应原文,排查时会省很多时间。

5. 智能体落地的关键一步:DeepSeek R1 的 Function Calling 完整示例

到这里 DEMO 已经能对话了,但“智能体”和“聊天机器人”的区别在于一个能力:模型能不能主动调用你提供的工具。DeepSeek 的 API 支持 function calling,也就是让模型根据用户问题生成一个结构化的工具调用请求,PHP 端负责执行真实函数并把结果返回给模型,最终由模型组织成自然语言回答。这一步是接入智能体的分水岭,下面给一个可用get_weather做演示的完整示例。

5.1 定义工具与执行函数:tools 参数的 JSON Schema 写法

$tools = [ [ 'type' => 'function', 'function' => [ 'name' => 'get_weather', 'description' => '查询指定城市的当前天气', 'parameters' => [ 'type' => 'object', 'properties' => [ 'city' => [ 'type' => 'string', 'description' => '城市名,如北京、上海', ], ], 'required' => ['city'], ], ], ], ]; function get_weather(string $city): string { $mock = ['北京' => '晴 25°C', '上海' => '小雨 22°C']; return $mock[$city] ?? ($city . ':暂不支持该城市'); }

tools参数里的 JSON Schema 遵循 OpenAPI 规范,模型根据description判断何时触发这个工具。description写得越具体,模型越不会在该调用的时候不调用、不该调用的时候乱调用。properties里的每个字段都要声明类型,required数组里写上必填参数,选填参数不要放进required

5.2 两段式请求:让工具结果作为下一步对话的输入

function chatWithTool(string $apiKey, string $userInput): string { $messages = [ ['role' => 'user', 'content' => $userInput], ]; $first = callDeepSeek($apiKey, $messages, $tools); // 模型要求调用工具时,返回的 finish_reason 是 tool_calls if (($first['choices'][0]['finish_reason'] ?? '') !== 'tool_calls') { return $first['choices'][0]['message']['content']; } $toolCall = $first['choices'][0]['message']['tool_calls'][0]; $args = json_decode($toolCall['function']['arguments'], true); $result = get_weather($args['city']); // 第一段返回的 assistant 消息带 tool_calls,必须原样放回 $assistantMsg = $first['choices'][0]['message']; unset($assistantMsg['reasoning_content']); $messages[] = $assistantMsg; $messages[] = [ 'role' => 'tool', 'tool_call_id' => $toolCall['id'], 'content' => $result, ]; $second = callDeepSeek($apiKey, $messages, $tools); return $second['choices'][0]['message']['content']; }

两段式流程是 Function Calling 的标准形态。第一段请求带上tools,模型不直接回答“北京天气如何”,而是返回一个tool_calls数据结构,里面包含函数名和 JSON 参数。PHP 端解析出city值,调用真实函数拿到结果,再以role=tool消息回传,并必须携带tool_call_id建立关联。第二段请求发回后,模型才把工具结果组织成自然语言。

给一个快速验证工具调用是否生效的方法:把第一次请求的返回 JSON 打印出来,检查finish_reason是否为tool_calls。如果模型直接返回了文本而不是工具调用,优先修正tools参数里的description描述,让模型更容易理解触发条件。生产环境可以把工具注册表独立成数组,批量注册订单查询、库存检查等业务函数,parameters里增加业务字段后,同一个两段式流程可以复用,不需要为每个工具重写调度逻辑。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 3:45:27

三年安卓老机卡顿排查:WorkBuddy四步优化与五倍提速实测

三年前买的这台手机&#xff0c;最近半年的状态基本可以用四个字概括&#xff1a;点啥卡啥。早上闹钟响了想按掉&#xff0c;屏幕得先愣两秒才亮&#xff1b;扫码付款的时候&#xff0c;后面排队的人已经付完了&#xff0c;我这边相机还没打开&#xff1b;最离谱的是相册&#…

作者头像 李华
网站建设 2026/9/17 3:43:36

Qt下FFmpeg+SDL播放器开发:从解码到录像截图完整实践

简介&#xff1a;这份QT环境下基于ffmpeg与SDL开发的音视频播放器工程源码&#xff0c;适合有一定C基础、希望学习多媒体播放器实现原理的开发者。资源完整演示了如何将ffmpeg的解码能力与SDL的渲染能力集成进QT界面&#xff0c;并额外实现录像与截图功能&#xff0c;覆盖YUV转…

作者头像 李华
网站建设 2026/9/17 3:43:23

基于约束差分进化算法的多微电网拓扑设计及Matlab实现

做过多微电网项目的人都有同感&#xff1a;大多数时候瓶颈不在“运行”而在“规划”。哪里架联络线、哪些微电网之间互联、要不要走冗余回路&#xff0c;这些拓扑设计问题一旦规模上来&#xff0c;组合数量是几何级数膨胀。传统的人工推演加启发式规则在小规模场景还能凑合&…

作者头像 李华
网站建设 2026/9/17 3:42:55

AI个性化不是设置,而是人机协作契约

1. 这不是“设置”&#xff0c;而是一场持续的AI关系经营“我的 AI 个性化设置”——看到这个标题&#xff0c;很多人第一反应是点开某个App里的齿轮图标&#xff0c;滑动几下 sliders&#xff0c;勾选几个“更懂我”的选项&#xff0c;然后期待AI突然变得像老朋友一样善解人意…

作者头像 李华