【异地访问本地DeepSeek】Flask+内网穿透,轻松实现本地DeepSeek的远程访问

写在前面:本博客仅作记录学习之用,部分图片来自网络,如需引用请注明出处,同时如有侵犯您的权益,请联系删除!



前言

在人工智能技术日新月异的今天,DeepSeek作为一款强大的大语言模型,已经在众多领域中展现出其巨大的应用潜力。然而,对于许多用户而言,在本地服务器或者电脑部署DeepSeek,异地如何访问本地资源,成为了一个值得思考的问题。

在这里插入图片描述

本文以内网穿透技术实现公网访问,以期为相关从业者或爱好者提供有价值的参考。本地部署DeepSeek,意味着用户可以在自己的服务器上运行这一大语言模型,从而在一定程度上掌控数据的隐私性和安全性。与此同时,通过内网穿透技术,用户还能将本地部署的DeepSeek实例暴露到公网上,实现远程访问和交互。不仅提高了模型的可用性,还为跨地域、跨团队的合作提供了极大的便利。

然而,本地部署和内网穿透并非没有挑战。硬件成本、维护成本、技术门槛以及安全风险等问题,都是用户在决策过程中需要考虑的关键因素。因此,本文旨在全面分析本地部署DeepSeek并通过内网穿透实现公网访问的必要性,帮助用户权衡利弊,做出最适合自己的选择。


依赖

本地部署教程: 【DeepSeek本地化部署保姆级教程 】

如果需要创建新环境:conda create -n deepseek python=3.7

  • Window 11
  • Flask
  • markdown2
  • requests

安装: pip install Flaskpip install markdown2

东荷新绿-DeepSeek东荷新绿-Gitee,欢迎访问,如有疑问,可评论区留言。

Flask构建本地网页访问

LM Studio 开启网址访问

确保加载合适的模型(GGUF),然后在开发者选项中确保开启运行,并查看对应的IP和端口,此处http://127.0.0.1:1234,这个区别于OpenAI。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

DeepSeek 调用模板

DeepSeek 调用模板:首次调用 API

# Please install OpenAI SDK first: `pip3 install openai`

from openai import OpenAI

client = OpenAI(api_key="<DeepSeek API Key>", base_url="https://api.deepseek.com")

response = client.chat.completions.create(
    model="deepseek-chat",
    messages=[
        {"role": "system", "content": "You are a helpful assistant"},
        {"role": "user", "content": "Hello"},
    ],
    stream=False
)

print(response.choices[0].message.content)

此处提供了OpenAI的调用方式,由于需要api_key显得比较麻烦或者需要付费,因此直接和LM Studio 的本地网址进行访问。

Flask 访问本地网址

通过上述模板构建本地的API,将Flask构建的网页 的输入框的内容传入到messages中,随后将其传入到LM Studio进行推理,将返回的结果进一步展示到Flask构建的对话框中即可。下面是app.py的逻辑:

from flask import Flask, request, jsonify, render_template
import markdown2
import requests

API_URL = "http://localhost:1234/v1/chat/completions"

headers = {
    "Content-Type": "application/json; charset=utf-8"
}

app = Flask(__name__, template_folder='templates',)
app.static_folder = 'static'
app.config["JSON_AS_ASCII"] = False
app.json.ensure_ascii = False


@app.route('/')
def index():
    return render_template("index.html")

@app.route('/process', methods=['POST'])
def process():
    data = request.get_json()
    # print(data)
    user_input = data['input']
    # user_input = data['user_input']
    # user_input = request.form['user_input']

    messages = [
        {"role": "system", "content": "你是一个优秀的人工智能助手"},
        {"role": "user", "content": f"{user_input}"},
    ]

    data = {
        "model": "DeepSeek/7B/DeepSeek-R1-Distill-Qwen-7B-Q6_K.gguf",  # 你的 DeepSeek 模型名称
        "messages": messages,
        "stream": False  # 关闭流式输出
    }

    response = requests.post(API_URL, headers=headers, json=data)

    if response.status_code == 200:
        result = response.json()
    else:
        print("请求失败:", response.status_code, response.text)

    # 将用户输入和脚本输出以对话形式返回
    conversation = [
        {"user": user_input},
        {"script": result["choices"][0]["message"]["content"]}
    ]
    html_content = conversation[1]["script"]
    think_message = html_content.split('</think>')[0] + '</think>'
    response_message = html_content.split('</think>')[1]

    think_message = markdown2.markdown(think_message)
    response_message = markdown2.markdown(response_message)

    response_message = f"DeepSeek: {response_message}"
    think_message = f"Think: {think_message}"

    return jsonify({'think': think_message, 'message': response_message})


if __name__ == '__main__':
    app.run(debug=False)

调试阶段可以使用app.run(debug=True),确定后改为False。

HTML内容

HTML可以使用大模型直接输出,下面是一个示范的提示词:

你的任务是使用python、Flask创建一个网页,具体要求如下:
1. 网页需包含两个文本框,一个用于获取用户输入,另一个用于显示Python脚本的返回内容。
2. 输入框需固定于网页中底部并居中,并长度仅为网页宽度50%,发送键位于输入框的右侧,发送键实现将输入框的文本发送给脚本
3. 输出框以对话的形式进行展示,同时位于位于输入框的上方并居中,并长度仅为网页宽度50%,展示用户的输入和脚本的返回内容
4. 网页需要有背景图
5. 只需要一个html和app.py

需要注意,如果无法加载图片,将其放在static文件夹中,配合app.static_folder = 'static'使用;如果网页显示中文乱码,结合app.config["JSON_AS_ASCII"] = False; app.json.ensure_ascii = False使用;
下面是不断调整后的一个html文件的内容:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>东荷新绿-DeepSeek</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS_HTML" async></script>
    <link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
    <style>
        body {
            margin: 0;
            font-family: Arial, sans-serif;
            background-image: url("../static/background.jpg");
            background-size: cover;
            background-position: center;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            height: 100vh;
        }
        .container {
            width: 55%;
            background-color: rgba(255, 255, 255, 0.8);
            padding: 20px;
            border-radius: 10px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
        }
        .chat-box {
            height: 400px;
            overflow-y: auto;
            border: 1px solid #ccc;
            padding: 10px;
            margin-bottom: 10px;
        }
        .input-group {
            display: flex;
            justify-content: space-between;
            align-items: center;
            width: 100%;
        }
        .input-group input {
            width: calc(100% - 10px);
            padding: 10px;
            border: 1px solid #ccc;
            border-radius: 5px;
        }
        .input-group button {
            width: calc(10% - 10px);
            max-width: 60px;
            min-width: 30px;
            height: 38px;
            padding: 10px;
            border: 1px solid #ccc;
            border-radius: 5px;
            background-color: #007bff;
            background-image: url("../static/send.png");
            background-size: contain;
            background-position: center;
            background-repeat: no-repeat;
        }
        .input-group button:hover {
            background-color: #0056b3;
        }

        #waitMessage {
            display: flex;
            text-align: center; /* 使内容居中 */
            margin-top: 2px;   /* 顶部外边距 */
            padding: 2px;      /* 内边距 */
        }
        #waitMessage img {
            max-width: 60px; /* 确保图片不会超出div的宽度 */
            max-height: 30px;    /* 保持图片的宽高比 */
            margin-right: 2px; /* 图片右侧间距 */
        }


    </style>
</head>
<body>

    <div class="container">
        <div class="chat-box" id="chat-box" contenteditable="false"></div> <!-- 设置 contenteditable 为 false 防止用户直接编辑 -->
        <div class="input-group">
            <input type="text" class="input-group input" id="user-input" placeholder="输入你的消息...">
            <button onclick="sendMessage()"></button>
        </div>
    </div>

    <script>
        const max_visit = 3;
        var limit_visit = 1;
        const waitmessageElement = document.createElement('div');
        waitmessageElement.id = 'waitMessage';
        const imgElement = document.createElement('img');

        alert('算力有限,目前仅支持三次问答!');

        document.getElementById('user-input').addEventListener("keyup", function (event) {
            if (event.key === 'Enter') {
                sendMessage();
            }
        });

        function reLoad() {
            const limit_visit_saved = sessionStorage.getItem('limit_visit');
            if (limit_visit_saved) {
                limit_visit = JSON.parse(limit_visit_saved);
            }
            else {
                limit_visit = 1;
            }
        }

        function checkAccess() {
            if (limit_visit > max_visit) {
                sessionStorage.setItem('limit_visit', JSON.stringify(limit_visit));
                alert('您已经超过访问限制, 请稍后再试。');
                document.getElementById('user-input').value = '';
            }
        }

        function appendMessage(message, isUser, wait) {
            const chatBox = document.getElementById('chat-box');
            const messageElement = document.createElement('div');
            messageElement.textContent = message;
            messageElement.style.color = isUser ? 'blue' : 'black';
            messageElement.style.padding = '5px';
            messageElement.style.borderBottom = '1px solid #ccc';
            if (wait !== true) {
                messageElement.innerHTML = message;
            }
            chatBox.appendChild(messageElement);
            if (wait) {
                imgElement.src = '../static/wait.gif';
                imgElement.alt = '正在深度思考,请耐心等待....';
                waitmessageElement.appendChild(imgElement);
                chatBox.appendChild(waitmessageElement);
            }
            chatBox.scrollTop = chatBox.scrollHeight;
        }

        function sendMessage() {
            checkAccess();
            const info = document.getElementById('user-input').value;
            if (info.trim() === '') {
                alert("输入信息不能为空!");
            }
            else if (imgElement.alt !== '') {
                alert("本轮回答尚未结束,请在本轮问答结束后提问!");
            }
            else{
                limit_visit += 1;
                const userInput = '用户:' + document.getElementById('user-input').value;
                document.getElementById('user-input').value = ''
                appendMessage(userInput, true, true);
                if (info.trim() !== '') {
                    const xhr = new XMLHttpRequest();
                    xhr.open('POST', '/process', true);
                    xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
                    xhr.onreadystatechange = function () {
                        if (xhr.readyState === 4 && xhr.status === 200) {
                            const response = JSON.parse(xhr.responseText);
                            document.getElementById('user-input').value = '';
                            appendMessage(response.message, false, false);
                            waitmessageElement.textContent = '';
                            imgElement.src = '';
                            imgElement.alt = '';
                            document.getElementById('user-input').value = '';
                        }
                    };
                    xhr.send(JSON.stringify({input: userInput}));
                }
            }
        }
        window.onload = reLoad();
    </script>
</body>
</html>

网页的结构如下:
在这里插入图片描述

本地推理

在输入框中输入问题后发送,获取7B模型的返回值,网址:http://127.0.0.1:5000,结果如下
在这里插入图片描述

LM Studio中的记录如下:

2025-03-07 20:16:55  [INFO] 
[LM STUDIO SERVER] Accumulating tokens ... (stream = false)
2025-03-07 20:18:02  [INFO] 
[LM STUDIO SERVER] [7b] Generated prediction:  {
  "id": "chatcmpl-7wb8kmz49ya229h23qsj",
  "object": "chat.completion",
  "created": 1741349814,
  "model": "7b",
  "choices": [
    {
      "index": 0,
      "logprobs": null,
      "finish_reason": "stop",
      "message": {
        "role": "assistant",
        "content": "<think>\n好,我现在要介绍重庆。首先,重庆是中国的一个直辖市,位于西南部,嘉陵江中下游。地理位置优越,连接长江、avesi和 Yangtze River,交通便利。\n\n然后,重庆有很多著名的景点,比如洪崖洞、解放碑、长江索道、大足石刻、武隆喀斯特地貌和磁器口古镇。这些都是了解重庆文化的重要地方。\n\n接下来是美食方面,重庆火锅是必尝的,还有小面、酸辣粉、山城码火锅和陈建平麻花这些特色小吃。此外,还有重庆的夜生活很丰富,有很多酒吧和咖啡馆。\n\n最后,重庆是一个充满活力的城市,适合旅游和居住。我可以把这些信息整理一下,让用户清楚了解重庆的情况。\n</think>\n\n当然!重庆是中国的一个直辖市,位于中国西南部,地处长江与嘉陵江交汇处,地势平坦,物产丰富。以下是关于重庆的一些详细介绍:\n\n### 1. 地理位置\n重庆是中国西南地区的中心,东邻湖北、湖南,南接贵州,西靠陕西,北连四川( Jaco 江)。由于地形平坦,重庆的交通和物资运输都非常便利。\n\n### 2. 历史文化\n重庆有着悠久的历史和丰富的文化传统。这里曾是抗日战争时期的陪都,也是中国西南地区的政治、经济和文化中心之一。重庆以其独特的城市风貌和多元的文化著称,比如解放碑、洪崖洞等地方都是历史的见证。\n\n### 3. 经济发展\n作为中国西南地区的经济重镇,重庆拥有丰富的工业基础和技术含量高的产业,如汽车制造、电子信息、装备制造等。此外,重庆还是长江经济带上的重要枢纽城市之一。\n\n### 4. 美食文化\n重庆以其独特的美食文化闻名于世。当地人们以麻辣口味著称,重庆火锅是其代表性的美食之一,此外还有小面、酸辣粉、山城码火锅和陈建平麻花等特色小吃。\n\n### 5. 景点与旅游\n重庆拥有众多著名景点,如洪崖洞、解放碑、长江索道、大足石刻、武隆喀斯特地貌(包括仙女山和金佛山)以及磁器口古镇等。这些景点不仅适合休闲观光,也适合家庭游玩。\n\n### 6. 自然景观\n重庆的自然景观同样丰富多采,尤其是武隆的喀斯特地貌群,被称为“世界自然遗产”,拥有众多 limestone溶洞和天生三桥等自然奇观。\n\n### 7. 气候与特色\n重庆的气候属于亚热带季风气候,四季分明。由于地形平坦,雨量充沛,夏季炎热潮湿,冬季则温和湿润。由于其地形地貌,重庆被称为“山城”,在晚上灯光下能看到城市的独特韵味。\n\n### 8. 交通\n重庆是一个交通枢纽城市,拥有发达的公路、铁路、航空和水运网络。例如,重庆江北国际机场是中国西部重要的航空枢纽,而长江索道是连接渝中区与沙坪坝区的重要交通工具之一。\n\n总的来说,重庆以其独特的地理位置、丰富的历史文化、多样的美食和自然景观吸引了无数游客。无论是休闲旅行还是商务活动,重庆都是一个非常适合的选择。"
      }
    }
  ],
  "usage": {
    "prompt_tokens": 15,
    "completion_tokens": 672,
    "total_tokens": 687
  },
  "stats": {},
  "system_fingerprint": "7b"
}

内网穿透访问

内网穿透工具较多,此处使用花生壳为例子。

  • 登录后,选择内网穿透
    在这里插入图片描述
  • 选择添加映射
    在这里插入图片描述
  • 填写映射信息

在这里插入图片描述

  • 查看公网网址
    在这里插入图片描述

  • 公网访问

在这里插入图片描述

总结

总结: 为了方便本地化部署后,异地调用本地模型进行推理,可以按照以下步骤操作:

  1. 基础环境:在LM Studio官网下载并运行对应的GGUF模型,以及安装对应的python包。
  2. Flask构建网页:Flask结合LM Studio的端口和HTML构建网页,实现网页对话。
  3. 内网穿透:通过花生壳将Flask所开放的端口进行映射,实现异地访问本地的模型。

互动

  • 你觉得上述内容对你有帮助吗?`

欢迎在评论区解答上述问题,分享你的经验和疑问!

当然,也欢迎一键三连给我鼓励和支持:👍点赞 📁 关注 💬评论。


致谢

欲尽善本文,因所视短浅,怎奈所书皆是瞽言蒭议。行文至此,诚向予助与余者致以谢意。


参考

[1] DeepSeek API 文档
[2] 新版Flask返回中文乱码解决,unicode编码


往期回顾


👆 DeepSeek本地化部署保姆级教程👆

👆 EfficientTrain++帮你降低网络训练的成本👆

👆 PyCharm环境下Git与Gitee联动👆

👆 Ping通但SSH连接失败的解决办法👆

👆 轻量化设计如何提高模型的推理速度👆

👆 正则化与正则剪枝👆
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

东荷新绿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值