支持显示思考过程,并在上下文拼接时过滤掉思考信息减少输入tokens
位置:plugin\ai\app\handler\DeepSeekReasoner.php
<?php
namespace plugin\ai\app\handler;
class DeepSeekReasoner extends Base
{
/**
* @var string 模型处理器名称
*/
protected static $name = 'DeepSeekReasoner';
/**
* @var string 模型类型
*/
protected static $type = 'deepseek-reasoner';
/**
* @var string[] 支持的模型名称
*/
public static $models = [
'deepseek-reasoner',
];
/**
* @var string[] 自定义配置
*/
public static $defaultSettings = [
'api' => [
'name' => 'API',
'type' => 'text',
'value' => 'https://api.deepseek.com',
],
'apikey' => [
'name' => 'ApiKey',
'type' => 'text',
'value' => '',
],
'regFreeCount' => [
'name' => '注册赠送',
'type' => 'number',
'value' => 0,
],
'dayFreeCount' => [
'name' => '每日赠送',
'type' => 'number',
'value' => 0,
],
];
/**
* @var string 处理器
*/
protected $driverClass = driver\DeepSeekReasoner::class;
/**
* 对话
* @param $data
* @param $options
* @return void
*/
public function completions($data, $options)
{
$this->driver = new $this->driverClass($this->getSettings());
$this->driver->completions($data, $options);
}
}
位置:plugin\ai\app\handler\driver\DeepSeekReasoner.php
<?php
namespace plugin\ai\app\handler\driver;
use Throwable;
class DeepSeekReasoner extends Gpt
{
/**
* @var string api地址
*/
protected $api = 'https://api.deepseek.com';
/**
* @var bool 是否正在推理
*/
protected $reasoning = false;
/**
* @param array $data
* @param array $options
* @return void
* @throws Throwable
*/
public function completions(array $data, array $options)
{
if (!empty($data['messages'])) {
$data['messages'] = array_map(function ($item) {
$start = "```thinking";
$end = "\n```\n\n------------\n\n";
if ($item['role'] === 'assistant' && strpos($item['content'], $start) === 0) {
$endPos = strpos($item['content'], $end);
if ($endPos !== false) {
$endPos += strlen($end);
$item['content'] = substr($item['content'], $endPos);
}
}
return $item;
}, $data['messages']);
}
if (isset($options['stream'])) {
$options['stream'] = function ($data) use ($options) {
$data = array_merge(['content' => ''], $data);
unset($data['model']);
$delta = $data['choices'][0]['delta'] ?? [];
$content = !empty($delta['content']) ? $delta['content'] : ($delta['reasoning_content'] ?? '');
if (!empty($delta['reasoning_content']) && empty($delta['content'])) {
if (!$this->reasoning) {
$this->reasoning = true;
$content = "```thinking\n" . $content;
}
} else if ($this->reasoning) {
$this->reasoning = false;
$content = "\n```\n\n------------\n\n" . $content;
}
if ($content === "<think>") {
$content = "```thinking\n";
} else if ($content === "</think>") {
$content = "\n```\n\n------------\n\n";
}
$data['content'] = $content;
$options['stream']($data);
};
}
return parent::completions($data, $options);
}
/**
* @param $buffer
* @return array|array[]|mixed
*/
public static function formatResponse($buffer)
{
if (!$buffer || $buffer[0] === '') {
return [
'error' => [
'code' => 'parse_error',
'message' => 'Empty response from api',
'detail' => $buffer
]
];
}
$json = json_decode($buffer, true);
if ($json) {
return $json;
}
$chunks = explode("\n", $buffer);
$content = '';
$finishReason = null;
$model = '';
$promptFilterResults = null;
$contentFilterResults = null;
$contentFilterOffsets = null;
$toolCalls = [];
$reasoning = false;
foreach ($chunks as $chunk) {
$chunk = trim($chunk);
if ($chunk === "") {
continue;
}
if (strpos($chunk, 'data:{') === 0) {
$chunk = substr($chunk, 5);
} else {
$chunk = substr($chunk, 6);
}
if ($chunk === "" || $chunk === "[DONE]") {
continue;
}
try {
$data = json_decode($chunk, true);
if (isset($data['model'])) {
$model = $data['model'];
}
if (isset($data['prompt_filter_results'])) {
$promptFilterResults = $data['prompt_filter_results'];
}
if (isset($data['error'])) {
$content .= $data['error']['message'] ?? "";
} else {
foreach ($data['choices'] ?? [] as $item) {
if (!empty($item['delta']['reasoning_content']) && empty($item['delta']['content'])) {
if (!$reasoning) {
$reasoning = true;
$content .= "```thinking\n";
}
$content .= $item['delta']['reasoning_content'] ?? '';
} else {
if ($reasoning) {
$reasoning = false;
$content .= "\n```\n\n------------\n\n";
}
if ($item['delta']['content'] === "<think>") {
$content .= "```thinking\n";
} else if ($item['delta']['content'] === "</think>") {
$content .= "\n```\n\n------------\n\n";
} else {
$content .= $item['delta']['content'] ?? '';
}
}
foreach ($item['delta']['tool_calls'] ?? [] as $function) {
if (isset($function['function']['name'])) {
$toolCalls[$function['index']] = $function;
} elseif (isset($function['function']['arguments'])) {
$toolCalls[$function['index']]['function']['arguments'] .= $function['function']['arguments'];
}
}
if (isset($item['finish_reason'])) {
$finishReason = $item['finish_reason'];
}
if (isset($item['content_filter_results'])) {
$contentFilterResults = $item['content_filter_results'];
}
if (isset($item['content_filter_offsets'])) {
$contentFilterOffsets = $item['content_filter_offsets'];
}
}
}
} catch (Throwable $e) {
echo $e;
}
}
$result = [
'choices' => [
[
'finish_reason' => $finishReason,
'index' => 0,
'message' => [
'role' => 'assistant',
'content' => $content,
],
]
],
'model' => $model,
];
if ($promptFilterResults) {
$result['prompt_filter_results'] = $promptFilterResults;
}
if ($contentFilterResults) {
$result['choices'][0]['content_filter_results'] = $contentFilterResults;
}
if ($contentFilterOffsets) {
$result['choices'][0]['content_filter_offsets'] = $contentFilterOffsets;
}
if ($toolCalls) {
$result['choices'][0]['message']['tool_calls'] = array_values($toolCalls);
}
return $result;
}
}
有点小bug,我在测试的时候碰到thinking跟回答混了。

发的什么内容?对接的是官方api还是第三方的
官方的,就发了个9.11跟9.8谁大,然后发现思考过程截断显示成回答,然后又在思考内容的样式显示结果。
牛
我这边没出现这个情况
我这个思考过程很长,我还没细看是不是输出了什么字符导致截断的。回头细看一下。您这个硅基是直接把api接口改成硅基的接口地址就可以了?
对 api、模型换了就行
好嘞,谢谢
https://github.com/hsk99/transfer-statistics
哥,你这个怎么都不更新了,年前还想用来着