大模型围栏-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
}

 

posted @ 2025-08-20 10:21  badwood  阅读(15)  评论(0)    收藏  举报
Badwood's Blog