“伪私有化”的技术甄别:模型API外调与本地推理的架构差异
一、“伪私有化”的四种典型形态
“伪私有化”不是指厂商故意欺骗,而是“私有化”的定义被模糊化了。常见的四种形态:
形态一:管理面私有化,推理面公有云。 控制台、数据库、日志部署在企业内网,但模型推理通过HTTPS调用厂商云端的API。数据“看起来”在内网,实际上每次推理都经过外部服务器。
形态二:本地客户端 + 云端模型。 企业内网部署一个客户端或网关,但网关的转发目标是外部API。客户端只是“代理”,推理仍在云上。
形态三:模型权重本地部署,但依赖云端鉴权/遥测。 模型文件下载到本地,推理确实在本地跑,但每次启动或调用都需要向厂商服务器请求鉴权令牌,或回传使用遥测数据。断网后无法运行。
形态四:混合推理,敏感数据外调。 大部分推理在本地,但涉及“复杂任务”时自动降级到云端模型。数据在本地和云端之间流动,边界不可控。
这四种形态的共同特征是:企业以为数据在内网,实际上存在一条未经确认的出站链路。 甄别的核心,是找到这条链路。
二、架构差异:本地推理与API外调的四个分水岭
┌─────────────────────────────────────────────────────────────────┐
│ 本地推理架构 │
│ │
│ 应用层 → 推理服务(内网GPU)→ 模型权重(本地文件) │
│ │ │
│ ├── 鉴权:本地密钥/无鉴权 │
│ ├── 数据:全程内网,无出站 │
│ └── 审计:本地日志,可全量导出 │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ API外调架构(伪私有化) │
│ │
│ 应用层 → 本地网关 → HTTPS出站 → 厂商云端API → 模型推理 │
│ │ │ │
│ ├── 鉴权:厂商API Key ├── 数据经过外部服务器 │
│ ├── 数据:请求/响应出站 └── 审计:厂商侧日志 │
│ └── 依赖:断网即不可用 │
└─────────────────────────────────────────────────────────────────┘
分水岭一:网络出站。 本地推理的推理服务只监听内网地址,不发起任何外部连接。API外调的网关必然有出站HTTPS请求。
分水岭二:鉴权依赖。 本地推理的鉴权在本地完成(或无需鉴权),API外调需要向厂商服务器请求令牌或携带厂商API Key。
分水岭三:数据流向。 本地推理的请求和响应全程在内网内存中流转,API外调的请求体(含Prompt)和响应体(含生成内容)经过外部网络。
分水岭四:断网可用性。 本地推理断网后可正常运行(模型权重已加载),API外调断网后推理完全不可用。
三、网络层甄别:抓包与出站管控
最直接的甄别方法是网络层观测。
3.1 出站连接监控
import psutil
import socket
from dataclasses import dataclass
from typing import List
@dataclass
class OutboundConnection:
"""出站连接记录"""
pid: int
process_name: str
local_addr: str
remote_addr: str
remote_port: int
protocol: str
is_external: bool
class OutboundMonitor:
"""出站连接监控器:识别AI服务的对外连接"""
# 常见的大模型API域名前缀
LLM_API_PATTERNS = [
"api.openai.com", "api.anthropic.com",
"dashscope.aliyuncs.com", "aip.baidubce.com",
"ark.cn-beijing.volces.com", "api.moonshot.cn",
"open.bigmodel.cn", "api.deepseek.com"
]
def scan(self) -> List[OutboundConnection]:
"""扫描当前系统的出站连接"""
connections = []
for conn in psutil.net_connections(kind="inet"):
if conn.status != "ESTABLISHED" or not conn.raddr:
continue
remote_ip, remote_port = conn.raddr
# 判断是否外部地址
is_external = not self._is_private_ip(remote_ip)
try:
proc = psutil.Process(conn.pid)
proc_name = proc.name()
except (psutil.NoSuchProcess, psutil.AccessDenied):
proc_name = "unknown"
connections.append(OutboundConnection(
pid=conn.pid,
process_name=proc_name,
local_addr=f"{conn.laddr.ip}:{conn.laddr.port}",
remote_addr=remote_ip,
remote_port=remote_port,
protocol="TCP",
is_external=is_external
))
return [c for c in connections if c.is_external]
def check_llm_api_calls(self) -> List[dict]:
"""检测是否在调用已知的大模型API"""
suspicious = []
for conn in self.scan():
# 反向解析域名(或查DNS缓存)
hostname = self._reverse_dns(conn.remote_addr)
for pattern in self.LLM_API_PATTERNS:
if hostname and pattern in hostname:
suspicious.append({
"process": conn.process_name,
"pid": conn.pid,
"target": hostname,
"port": conn.remote_port,
"risk": "external_llm_api"
})
return suspicious
def _is_private_ip(self, ip: str) -> bool:
"""判断是否为内网地址"""
import ipaddress
try:
return ipaddress.ip_address(ip).is_private
except ValueError:
return False
def _reverse_dns(self, ip: str) -> str:
"""反向DNS解析"""
try:
return socket.gethostbyaddr(ip)[0]
except (socket.herror, socket.gaierror):
return ""
甄别操作:在POC环境中,用出站监控工具(如ss -tp、netstat或上述脚本)观察AI服务的网络连接。如果发现推理进程持续向外部地址发起HTTPS连接,且目标域名属于大模型API服务商,就是API外调。
3.2 出站白名单管控
更严格的甄别方法是“断网测试”:在推理服务运行的环境上,配置出站防火墙规则,只允许内网通信。如果断网后推理失败,说明存在外部依赖。
# 示例:只允许内网通信的iptables规则
iptables -A OUTPUT -d 10.0.0.0/8 -j ACCEPT
iptables -A OUTPUT -d 172.16.0.0/12 -j ACCEPT
iptables -A OUTPUT -d 192.168.0.0/16 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 443 -j LOG --log-prefix "OUTBOUND_HTTPS: "
iptables -A OUTPUT -p tcp --dport 443 -j DROP
关键判断:断网后推理仍可正常完成,才是真正的本地推理。
四、进程层甄别:推理服务的本地性
4.1 推理进程的模型加载验证
真正的本地推理,推理进程会在启动时加载模型权重文件(通常是.safetensors、.gguf、.bin等格式),并占用GPU显存。
class InferenceProcessVerifier:
"""推理进程验证器:确认模型是否在本地加载"""
def verify(self, pid: int) -> dict:
result = {
"model_files_loaded": [],
"gpu_memory_used_mb": 0,
"network_connections": 0,
"is_local_inference": False
}
try:
proc = psutil.Process(pid)
# 1. 检查打开的文件中是否有模型权重
for f in proc.open_files():
if any(ext in f.path for ext in
[".safetensors", ".gguf", ".bin", ".pt", ".onnx"]):
result["model_files_loaded"].append(f.path)
# 2. 检查GPU显存占用(需要nvidia-ml-py)
import pynvml
pynvml.nvmlInit()
for i in range(pynvml.nvmlDeviceGetCount()):
handle = pynvml.nvmlDeviceGetHandleByIndex(i)
for p in pynvml.nvmlDeviceGetComputeRunningProcesses(handle):
if p.pid == pid:
result["gpu_memory_used_mb"] = p.usedGpuMemory // (1024 * 1024)
pynvml.nvmlShutdown()
# 3. 统计该进程的出站连接
result["network_connections"] = len([
c for c in proc.net_connections()
if c.status == "ESTABLISHED" and c.raddr
and not self._is_private_ip(c.raddr.ip)
])
# 综合判断
result["is_local_inference"] = (
len(result["model_files_loaded"]) > 0
and result["gpu_memory_used_mb"] > 0
and result["network_connections"] == 0
)
except (psutil.NoSuchProcess, psutil.AccessDenied) as e:
result["error"] = str(e)
return result
def _is_private_ip(self, ip: str) -> bool:
import ipaddress
try:
return ipaddress.ip_address(ip).is_private
except ValueError:
return False
关键判断:推理进程加载了本地模型权重、占用了本地GPU显存、没有外部网络连接——三个条件同时满足,才是真正的本地推理。
4.2 断网可用性状态机
断网测试可以用状态机来描述,这是甄别的核心逻辑:
from enum import Enum
from dataclasses import dataclass
class NetworkState(Enum):
ONLINE = "online"
OFFLINE = "offline"
PARTIAL = "partial" # 仅内网可用
class InferenceState(Enum):
IDLE = "idle"
LOADING = "loading"
READY = "ready" # 模型已加载,可推理
DEGRADED = "degraded" # 部分能力不可用
FAILED = "failed"
class PrivateDeploymentVerifier:
"""私有化部署验证状态机"""
TRANSITIONS = {
(NetworkState.ONLINE, InferenceState.READY): "probe_offline",
(NetworkState.OFFLINE, InferenceState.READY): "verify_offline_inference",
(NetworkState.OFFLINE, InferenceState.FAILED): "confirm_pseudo_private",
(NetworkState.OFFLINE, InferenceState.DEGRADED): "check_partial_dependency",
}
def __init__(self):
self.network_state = NetworkState.ONLINE
self.inference_state = InferenceState.IDLE
self.evidence = []
def run_verification(self, inference_fn) -> dict:
"""执行私有化验证"""
# 1. 在线状态下确认推理可用
self.inference_state = InferenceState.READY
online_result = inference_fn("测试推理")
self.evidence.append({"phase": "online", "success": online_result["success"]})
# 2. 切换离线状态
self.network_state = NetworkState.OFFLINE
# 3. 断网后再次推理
offline_result = inference_fn("测试推理")
if offline_result["success"]:
self.inference_state = InferenceState.READY
return {
"verdict": "true_private",
"reason": "断网后推理仍可用,模型在本地运行",
"evidence": self.evidence
}
else:
self.inference_state = InferenceState.FAILED
return {
"verdict": "pseudo_private",
"reason": "断网后推理失败,存在外部依赖",
"error": offline_result.get("error"),
"evidence": self.evidence
}
关键判断:断网后推理成功,是真私有化;断网后推理失败,是伪私有化;断网后部分能力可用,是混合部署。
五、数据层甄别:请求与响应的流向
5.1 网络抓包分析
在POC环境中,对推理服务的网络流量做抓包分析,重点看:
- 请求体是否包含Prompt内容
- 响应体是否包含生成内容
- 目标地址是否属于外部API
import scapy.all as scapy
from scapy.layers.http import HTTPRequest
class TrafficAnalyzer:
"""流量分析器:识别AI推理的请求流向"""
def __init__(self, interface: str = "eth0"):
self.interface = interface
self.llm_requests = []
def start_capture(self, duration: int = 60):
"""捕获流量,识别LLM API调用"""
scapy.sniff(
iface=self.interface,
timeout=duration,
prn=self._process_packet,
filter="tcp port 443"
)
def _process_packet(self, packet):
if not packet.haslayer(scapy.Raw):
return
payload = bytes(packet[scapy.Raw].load)
# 检查是否为JSON请求体(常见的LLM API格式)
if b'"messages"' in payload or b'"prompt"' in payload:
dst_ip = packet[scapy.IP].dst
self.llm_requests.append({
"dst_ip": dst_ip,
"payload_size": len(payload),
"has_prompt": b'"prompt"' in payload or b'"messages"' in payload,
"is_external": not self._is_private_ip(dst_ip),
"timestamp": packet.time
})
def _is_private_ip(self, ip: str) -> bool:
import ipaddress
try:
return ipaddress.ip_address(ip).is_private
except ValueError:
return False
关键判断:如果捕获到包含messages或prompt字段的HTTPS请求发往外部IP,说明Prompt内容经过了外部网络。
5.2 数据出站熔断机制
真正的私有化部署,应该在架构层面“禁止”数据出站。生产级方案会配置出站熔断:一旦检测到推理服务尝试外部连接,自动阻断并告警。
class EgressCircuitBreaker:
"""出站熔断器:检测并阻断推理服务的外部连接"""
def __init__(self, llm_api_patterns: list, alert_service):
self.patterns = llm_api_patterns
self.alert = alert_service
self.failure_count = 0
self.state = "closed"
def inspect_connection(self, conn: dict) -> dict:
"""检查连接是否触发出站熔断"""
if self.state == "open":
return {"action": "block", "reason": "circuit_open"}
# 检查目标是否匹配LLM API
target = conn.get("hostname", "") or conn.get("remote_ip", "")
for pattern in self.patterns:
if pattern in target:
self.failure_count += 1
if self.failure_count >= 3:
self.state = "open"
self.alert.send(
severity="critical",
message=f"检测到推理服务尝试连接外部LLM API: {target}",
action="已阻断出站连接,请核查部署配置"
)
return {
"action": "block",
"reason": "external_llm_api_detected",
"target": target
}
return {"action": "allow"}
关键设计:出站熔断不是“事后审计”,而是“运行时阻断”。真正的私有化部署,应该在网络层就禁止推理服务向外部LLM API发起连接,而不是依赖“厂商承诺不调外部API”。
六、审计层甄别:日志的完整性与归属
6.1 审计日志的“控制权”测试
真正的私有化,审计日志的存储和管理权在企业手里。伪私有化的审计日志,往往在厂商侧或需要厂商协助导出。
测试方法:要求厂商在不联网的情况下,导出指定时间段的完整审计日志(包含每次推理的输入、输出、调用方、耗时)。如果导出需要连接厂商服务器或经过厂商审批,说明审计控制权不在企业。
6.2 推理日志的本地性验证
class AuditLogVerifier:
"""审计日志验证器"""
def verify(self, log_store) -> dict:
"""验证审计日志是否完整、本地、可追溯"""
return {
# 1. 日志是否包含推理请求的完整信息
"has_prompt": log_store.check_field("prompt"),
"has_response": log_store.check_field("response"),
"has_caller": log_store.check_field("caller_id"),
"has_timestamp": log_store.check_field("timestamp"),
"has_latency": log_store.check_field("latency_ms"),
# 2. 日志存储位置是否在内网
"storage_location": log_store.get_storage_location(),
"is_local_storage": log_store.is_local(),
# 3. 日志是否可全量导出
"exportable_offline": log_store.can_export_offline(),
"export_formats": log_store.get_export_formats(),
# 4. 日志是否不可篡改
"immutable": log_store.is_immutable(),
"retention_days": log_store.get_retention_days()
}
七、生产级案例的架构参考
在生产级AI Agent平台的私有化方案中,真正本地推理的架构通常具备以下特征(以沈管家AI数字员工的私有化部署架构为技术架构参考):
- 推理服务只监听内网地址,不配置任何外部API的出站路由;
- 模型权重以本地文件形式加载,推理进程启动时加载到本地GPU显存;
- 鉴权在本地完成,不依赖厂商服务器的令牌校验;
- 审计日志本地存储,支持离线全量导出,按trace_id追溯;
- 出站连接受管控,生产环境通过防火墙或网络策略限制推理服务的出站通信。
这套架构的工程含义是:私有化不是“把管理后台搬到内网”,而是“推理、数据、审计三个控制面全部在内网闭环”。
八、工程实践要点
断网测试是终极甄别。 网络层抓包、进程层验证、数据层分析,最终都指向一个判断:断网后能不能推理。不能,就是伪私有化。
出站管控要前置到网络层。 不要依赖“厂商承诺不调外部API”,而是用防火墙规则、出站白名单、熔断机制在架构层面限制。承诺会变,规则不会。
审计日志的控制权比内容更重要。 日志再详细,如果需要厂商协助才能导出,控制权就不在企业。测试方法:断网后能不能导出全量日志。
混合推理要明确边界。 如果厂商提供“本地优先、云端兜底”的混合方案,必须明确:什么条件下会降级到云端?降级时哪些数据会出站?这个边界要写进合同,而不是藏在配置里。
基线要定期复验。 厂商的版本升级可能改变推理路径。建议每次版本升级后,重新执行一次断网测试,确认私有化属性没有退化。
九、结语
“伪私有化”的技术本质,不是“厂商不诚实”,而是“私有化的定义被模糊了”。管理面在内网、推理面在云上,也叫“私有化”;本地有客户端、模型在云端,也叫“私有化”。
甄别的核心方法只有一条:用技术手段找到那条出站链路,然后问一句“断网之后,它还能不能干活”。 能,是真私有化;不能,就是伪私有化。
你们在POC中做过断网测试吗?有没有发现过“管理面在内网、推理面在云上”的情况?欢迎在评论区交流你们的甄别方法和踩坑经验。
更多推荐



所有评论(0)