为服务器DeepSeek服务添加API密钥及Flask代理故障排查
问题:Flask代理Ollama部署的DeepSeek模型时,验证通过后无法转发聊天请求
环境与背景
- CentOS7.9服务器上用Ollama部署了deepseek-r1:7b和1.5b模型,Nginx将
http://127.0.0.1:11434/转发至http://my-ip:8088,Win11客户端(如Cherry Studio)可正常调用API - 为服务添加API密钥验证,已将密钥、过期日期、用户IP写入
Api_key.txt,内容示例:
$ head Api_key.txt key-xxyy11,2025/2/24,10.12.100.200 key-xxyy22,2025/3/24,10.12.200.201
遇到的问题
getModels()函数正常,Cherry Studio可获取模型列表- 使用错误API密钥时,Cherry Studio能收到错误提示,验证逻辑生效
- 使用正确API密钥时,Cherry Studio无任何响应,无法与DeepSeek模型聊天
代码问题分析
原代码的核心问题有3点:
- 消息格式错误:
request.json.get('messages')获取的是完整的消息数组,但代码错误地将整个数组作为单条消息的content传入,导致Ollama收到非法格式的请求 - 流式响应处理错误:Ollama开启
stream: True后返回的是分块SSE响应,原代码用jsonify(response.json())尝试解析成JSON,无法正确转发流式内容,客户端收不到数据 - 硬编码模型参数:固定使用
deepseek-r1:1.5b模型,忽略了客户端请求中的指定模型
修复后的代码
from flask import Flask, request, jsonify, Response, stream_with_context import datetime import csv import requests from functools import lru_cache app = Flask(__name__) # 缓存API密钥,避免每次请求读取文件 @lru_cache(maxsize=None) def load_api_keys(): api_keys = {} with open('/path/to/Api_key.txt', mode='r') as file: reader = csv.reader(file) for row in reader: # 存储密钥对应的过期日期和IP api_keys[row[0]] = { 'expiry': row[1], 'allowed_ip': row[2] } return api_keys # 获取模型列表:正常工作,保留原逻辑 @app.route('/deepseek/models', methods=['GET']) def getModels(): url = "http://127.0.0.1:11434/v1/models" headers = { "Content-Type": "application/json" } response = requests.get(url, headers=headers) if response.status_code == 200: json_response = response.json() else: json_response={"Error":"get model list error from deepseek"} return jsonify(json_response), response.status_code # 处理聊天请求:修复流式转发和格式问题 @app.route('/deepseek/chat/completions', methods=['POST']) def chat(): # 获取并校验API密钥 auth_header = request.headers.get('Authorization') if not auth_header or not auth_header.startswith('Bearer '): return jsonify({"error": "Invalid Authorization header format."}), 401 api_key = auth_header.split(" ")[1] api_keys = load_api_keys() if api_key not in api_keys: return jsonify({"error": "Invalid API key."}), 403 key_info = api_keys[api_key] expiry_date = datetime.datetime.strptime(key_info['expiry'], "%Y/%m/%d") if expiry_date < datetime.datetime.now(): return jsonify({"error": "API key has expired."}), 403 # 可选:添加IP校验 # client_ip = request.remote_addr # if client_ip != key_info['allowed_ip']: # return jsonify({"error": "IP not allowed."}), 403 # 直接转发客户端的请求参数,不硬编码 payload = request.json # 确保stream参数和客户端一致,或者强制开启 # payload['stream'] = True base_url = "http://127.0.0.1:11434/v1/chat/completions" headers = { "Content-Type": "application/json" } # 发送流式请求到Ollama,使用stream=True try: response = requests.post( base_url, headers=headers, json=payload, stream=True ) response.raise_for_status() # 流式转发响应给客户端 return Response( stream_with_context(response.iter_content(chunk_size=1024)), content_type=response.headers.get('Content-Type'), status=response.status_code ) except requests.exceptions.RequestException as e: return jsonify({"error": f"Failed to forward request: {str(e)}"}), 500 if __name__ == '__main__': print("Proxy service running on 0.0.0.0:8089") app.run(host='0.0.0.0', port=8089, debug=True)
修复说明
- 缓存API密钥:用
lru_cache缓存密钥信息,避免每次请求读取文件,提升效率 - 正确转发请求参数:直接使用客户端的
messages和model参数,不再硬编码,保证请求格式正确 - 流式响应处理:使用
stream_with_context和Flask的Response直接转发Ollama的分块SSE响应,客户端能正常接收流式输出 - 完善错误处理:添加Authorization头格式校验,捕获请求异常
- 可选IP校验:保留IP校验代码注释,需要时可启用
内容的提问来源于stack exchange,提问作者George W
相关产品推荐
相关产品推荐

