DeepSeek 推理模型接入Webman Ai 显示思考过程

hsk99

支持显示思考过程,并在上下文拼接时过滤掉思考信息减少输入tokens

体验地址

https://mjai.top/#role=55

效果图

截图

代码

位置: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;
    }
}
496 4 1
4个评论

ascuge

有点小bug,我在测试的时候碰到thinking跟回答混了。
截图

  • hsk99 11天前

    发的什么内容?对接的是官方api还是第三方的

  • ascuge 11天前

    官方的,就发了个9.11跟9.8谁大,然后发现思考过程截断显示成回答,然后又在思考内容的样式显示结果。

wocall

  • 暂无评论
hsk99

我这边没出现这个情况

官方的api

截图
截图
截图


硅基流动的api

截图
截图

  • ascuge 11天前

    我这个思考过程很长,我还没细看是不是输出了什么字符导致截断的。回头细看一下。您这个硅基是直接把api接口改成硅基的接口地址就可以了?

  • hsk99 11天前

    对 api、模型换了就行

  • ascuge 11天前

    好嘞,谢谢

owenzhang

https://github.com/hsk99/transfer-statistics
哥,你这个怎么都不更新了,年前还想用来着

  • 暂无评论

hsk99

225
积分
0
获赞数
0
粉丝数
2019-06-29 加入
×
🔝