大模型围栏-nginx+lua
通过vllm框架实现了本地大模型服务化,通过oneapi实现服务接口鉴权,为了串入大模型围栏功能,在vllm和oneapi之间加入nginx,并通过lua脚本调用大模型围栏,根据围栏接口返回的情况放通/禁止服务。
1、nginx配置关键块:
http{ #lua脚本路径,fence.lua、cjson.so放在/usr/local/openresty/lualib/,还有一些必要的lua库(hmac.lua、http.lua、http_connect.lua、http_headers.lua),放在/usr/local/openresty/lualib/resty/ lua_load_resty_core off; lua_package_path '/usr/local/openresty/lualib/?.lua;;'; lua_package_cpath '/usr/local/openresty/lualib/?.so;;'; server { location / { access_by_lua_block { -- 只检查POST请求 if ngx.req.get_method() ~= "POST" then return end -- 读取请求体 ngx.req.read_body() local query = ngx.req.get_body_data() or "" -- 调用安全检查脚本 local security_check = require "fence" local security_status = security_check.main(query) -- 如果安全检查不通过,直接返回 if not security_status then ngx.header.content_type = 'application/json' ngx.status = ngx.HTTP_OK ngx.say('{"text": "我无法回答"}') ngx.exit(ngx.HTTP_OK) end } } }
2、fence.lua脚本:完成2件事,使用query调用api、判断api返回结果特定字段是否为true
local cjson = require "cjson.safe" local resty_http = require "resty.http" local resty_hmac = require "resty.hmac" local resty_sha256 = require "resty.sha256" local uuid = require "resty.uuid" -- 配置信息 local API_URL = "api_url" local FORMAT_SIGN = "%s/%s/%s" local ACCESS_KEY_ID = "ACCESS_KEY_ID" local ACCESS_KEY_SECRET = "ACCESS_KEY_SECRET" -- 生成nonce local function generate_nonce() local raw_uuid = uuid.generate() local encoded = ngx.encode_base64(raw_uuid) encoded = encoded:gsub("+", "-"):gsub("/", "_"):gsub("=", "") return raw_uuid end -- 生成签名 local function generate_signature(data, secret) local hmac_sha256 = resty_hmac:new(secret, resty_hmac.ALGOS.SHA256) if not hmac_sha256 then ngx.log(ngx.ERR, "fence error: failed to create hmac_sha256 object") return nil end local ok = hmac_sha256:update(data) if not ok then ngx.log(ngx.ERR, "fence error: failed to add data") return nil end local signature = hmac_sha256:final() return ngx.encode_base64(signature) end local function get_check_status(response, default_value) -- 检查response是否为table且包含data字段 if type(response) ~= "table" or type(response.data) ~= "table" then return default_value end -- 直接返回securityCheckStatus,如果不存在则返回缺省值 if response.data.securityCheckStatus ~= nil then return response.data.securityCheckStatus else return default_value end end -- API调用 - 简化返回,只返回securityCheckStatusId local function api_call(nonce, signature, timestamp, user_id, session_id, message_id, query, default_value) local payload = cjson.encode({ tenantId = -13, userId = user_id, robotCode = "text2text", robotType = "robotType", content = query, contentType = "userQuery", sessionId = session_id, messageId = message_id }) local headers = { ['AccessKeyId'] = ACCESS_KEY_ID, ['Content-Type'] = 'application/json', ["Timestamp"] = timestamp, ["Nonce"] = nonce, ["Signature"] = signature, } local http = resty_http.new() local res, err = http:request_uri(API_URL, { method = "POST", body = payload, headers = headers, ssl_verify = false, timeout = 2000 -- 添加2秒超时 }) if not res then ngx.log(ngx.ERR, "fence error: API request failed: ", err) return default_value -- 失败 end if res.status ~= 200 then ngx.log(ngx.ERR, "fence error: API request failed with status: ", res.status) return default_value -- 失败 end local result = cjson.decode(res.body) if not result then ngx.log(ngx.ERR, "fence error: failed to decode response body") return default_value end local security_check_status = get_check_status(result, default_value) return security_check_status end -- 主函数 local function main(query, default_value) default_value = default_value == nil and true or default_value -- 设置默认值为true,避免因围栏接口失效导致模型无法调用 -- 生成鉴权信息 local timestamp = tostring(ngx.now() * 1000) -- 当前时间的毫秒级时间戳 local nonce = generate_nonce() local user_id = "user_id" local session_id = 'sid1111111' --uuid.generate() local message_id = 'mid2222222' --uuid.generate() local string_to_sign = FORMAT_SIGN:format(ACCESS_KEY_ID, timestamp, nonce) local signature = generate_signature(string_to_sign, ACCESS_KEY_SECRET) if not signature then ngx.log(ngx.ERR, "fence error: failed to generate signature") return default_value -- 失败 end return api_call(nonce, signature, timestamp, user_id, session_id, message_id, query, default_value) end -- 导出主函数 return { main = main }