[{"id":"deepseek_r1","user_id":"2ab9b883-88ba-4e20-b450-e3d3d1396070","name":"DeepSeek R1","type":"pipe","content":"\"\"\"\ntitle: DeepSeek R1\noriginal author: zgccrui\ndescription: 在OpwenWebUI中显示DeepSeek R1模型的思维链 - 仅支持0.5.6及以上版本。修复了一些小问题，可以适配更多平台。\nversion: 1.2.10-1\nlicence: MIT\n\"\"\"\n\nimport json\nimport httpx\nimport re\nfrom typing import AsyncGenerator, Callable, Awaitable\nfrom pydantic import BaseModel, Field\nimport asyncio\n\n\nclass Pipe:\n    class Valves(BaseModel):\n        DEEPSEEK_API_BASE_URL: str = Field(\n            default=\"https://cloud.infini-ai.com/maas/v1\",\n            description=\"DeepSeek API的基础请求地址\",\n        )\n        DEEPSEEK_API_KEY: str = Field(\n            default=\"\", description=\"用于身份验证的DeepSeek API密钥，可从控制台获取\"\n        )\n        DEEPSEEK_API_MODEL: str = Field(\n            default=\"deepseek-r1\",\n            description=\"API请求的模型名称，默认为 deepseek-r1 \",\n        )\n\n    def __init__(self):\n        self.valves = self.Valves()\n        self.data_prefix = \"data:\"\n        self.emitter = None\n\n    def pipes(self):\n        return [\n            {\n                \"id\": self.valves.DEEPSEEK_API_MODEL,\n                \"name\": self.valves.DEEPSEEK_API_MODEL,\n            }\n        ]\n\n    async def pipe(\n        self, body: dict, __event_emitter__: Callable[[dict], Awaitable[None]] = None\n    ) -> AsyncGenerator[str, None]:\n        \"\"\"主处理管道（已移除缓冲）\"\"\"\n        thinking_state = {\"thinking\": -1}  # 使用字典来存储thinking状态\n        self.emitter = __event_emitter__\n\n        # 验证配置\n        if not self.valves.DEEPSEEK_API_KEY:\n            yield json.dumps({\"error\": \"未配置API密钥\"}, ensure_ascii=False)\n            return\n\n        # 准备请求参数\n        headers = {\n            \"Authorization\": f\"Bearer {self.valves.DEEPSEEK_API_KEY}\",\n            \"Content-Type\": \"application/json\",\n        }\n\n        try:\n            # 模型ID提取\n            model_id = body[\"model\"].split(\".\", 1)[-1]\n            payload = {**body, \"model\": model_id}\n\n            # 处理消息以防止连续的相同角色\n            messages = payload[\"messages\"]\n            i = 0\n            while i < len(messages) - 1:\n                if messages[i][\"role\"] == messages[i + 1][\"role\"]:\n                    # 插入具有替代角色的占位符消息\n                    alternate_role = (\n                        \"assistant\" if messages[i][\"role\"] == \"user\" else \"user\"\n                    )\n                    messages.insert(\n                        i + 1,\n                        {\"role\": alternate_role, \"content\": \"[Unfinished thinking]\"},\n                    )\n                i += 1\n\n            # yield json.dumps(payload, ensure_ascii=False)\n\n            # 发起API请求\n            async with httpx.AsyncClient(http2=True) as client:\n                async with client.stream(\n                    \"POST\",\n                    f\"{self.valves.DEEPSEEK_API_BASE_URL}/chat/completions\",\n                    json=payload,\n                    headers=headers,\n                    timeout=300,\n                ) as response:\n                    # 错误处理\n                    if response.status_code != 200:\n                        error = await response.aread()\n                        yield self._format_error(response.status_code, error)\n                        return\n\n                    # 流式处理响应\n                    async for line in response.aiter_lines():\n                        if not line.startswith(self.data_prefix):\n                            continue\n\n                        # 截取 JSON 字符串\n                        json_str = line[len(self.data_prefix) :]\n\n                        # 去除首尾空格后检查是否为结束标记\n                        if json_str.strip() == \"[DONE]\":\n                            return\n\n                        try:\n                            data = json.loads(json_str)\n                        except json.JSONDecodeError as e:\n                            # 格式化错误信息，这里传入错误类型和详细原因（包括出错内容和异常信息）\n                            error_detail = f\"解析失败 - 内容：{json_str}，原因：{e}\"\n                            yield self._format_error(\"JSONDecodeError\", error_detail)\n                            return\n\n                        choices = data.get(\"choices\", [{}])\n                        if not isinstance(choices, list) or len(choices) == 0:\n                            return\n                        choice = choices[0]\n\n                        # 结束条件判断\n                        if choice.get(\"finish_reason\"):\n                            return\n\n                        # 状态机处理\n                        state_output = await self._update_thinking_state(\n                            choice.get(\"delta\", {}), thinking_state\n                        )\n                        if state_output:\n                            yield state_output  # 直接发送状态标记\n                            if state_output == \"<think>\":\n                                yield \"\\n\"\n\n                        # 内容处理并立即发送\n                        content = self._process_content(choice[\"delta\"])\n                        if content:\n                            if content.startswith(\"<think>\"):\n                                match = re.match(r\"^<think>\", content)\n                                if match:\n                                    content = re.sub(r\"^<think>\", \"\", content)\n                                    yield \"<think>\"\n                                    await asyncio.sleep(0.1)\n                                    yield \"\\n\"\n\n                            elif content.startswith(\"</think>\"):\n                                match = re.match(r\"^</think>\", content)\n                                if match:\n                                    content = re.sub(r\"^</think>\", \"\", content)\n                                    yield \"</think>\"\n                                    await asyncio.sleep(0.1)\n                                    yield \"\\n\"\n                            yield content\n\n        except Exception as e:\n            yield self._format_exception(e)\n\n    async def _update_thinking_state(self, delta: dict, thinking_state: dict) -> str:\n        \"\"\"更新思考状态机（简化版）\"\"\"\n        state_output = \"\"\n\n        # 状态转换：未开始 -> 思考中\n        if thinking_state[\"thinking\"] == -1 and delta.get(\"reasoning_content\"):\n            thinking_state[\"thinking\"] = 0\n            state_output = \"<think>\"\n\n        # 状态转换：思考中 -> 已回答\n        elif (\n            thinking_state[\"thinking\"] == 0\n            and not delta.get(\"reasoning_content\")\n            and delta.get(\"content\")\n        ):\n            thinking_state[\"thinking\"] = 1\n            state_output = \"\\n</think>\\n\\n\"\n\n        return state_output\n\n    def _process_content(self, delta: dict) -> str:\n        \"\"\"直接返回处理后的内容\"\"\"\n        return delta.get(\"reasoning_content\", \"\") or delta.get(\"content\", \"\")\n\n    def _format_error(self, status_code: int, error: bytes) -> str:\n        # 如果 error 已经是字符串，则无需 decode\n        if isinstance(error, str):\n            error_str = error\n        else:\n            error_str = error.decode(errors=\"ignore\")\n\n        try:\n            err_msg = json.loads(error_str).get(\"message\", error_str)[:200]\n        except Exception as e:\n            err_msg = error_str[:200]\n        return json.dumps(\n            {\"error\": f\"HTTP {status_code}: {err_msg}\"}, ensure_ascii=False\n        )\n\n    def _format_exception(self, e: Exception) -> str:\n        \"\"\"异常格式化保持不变\"\"\"\n        err_type = type(e).__name__\n        return json.dumps({\"error\": f\"{err_type}: {str(e)}\"}, ensure_ascii=False)\n","meta":{"description":"Display the reasoning chain of the DeepSeek R1 model in OpwenWebUI","manifest":{"title":"DeepSeek R1","description":"在OpwenWebUI中显示DeepSeek R1模型的思维链 - 仅支持0.5.6及以上版本。修复了一些小问题，可以适配更多平台。","version":"1.2.10-1","licence":"MIT"}},"is_active":false,"is_global":false,"updated_at":1740472444,"created_at":1740472444}]