Add cross-border-ecommerce/flairgs-vision-api
This commit is contained in:
@@ -0,0 +1,318 @@
|
||||
---
|
||||
name: flairgs-vision-api
|
||||
title: FlairGS 视觉分析 API
|
||||
description: Self-hosted VL (qwen3-vl:8b) + image processing API for 9Webs cross-border ecommerce — OCR, captcha, product analysis, IP screening, white-bg, txt2img. 关键:字段名是 base64 不是 image_base64。
|
||||
category: cross-border-ecommerce
|
||||
tags: [flairgs, vision, vl, ocr, captcha, qwen-vl, image-processing, cdp-bridge]
|
||||
---
|
||||
|
||||
# FlairGS 视觉分析 API
|
||||
|
||||
自建 VL (Vision-Language) + 图像处理 API。GS3 运行 qwen3-vl:8b,通过 frp 隧道暴露给 SG5。
|
||||
|
||||
## 速查表
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|-----|
|
||||
| **LAN URL (优先)** | `http://192.168.9.105:7870` |
|
||||
| **FRP URL (新)** | `http://43.160.244.125:17870` (2026-07 上线) |
|
||||
| **FRP URL (旧/回退)** | `http://frp.9webs.online:17870` |
|
||||
| **AtomK 工具箱** | `http://192.168.9.105:20261` (Desktop CDP 可访问) |
|
||||
| **连接策略** | FRP 直连优先(10s 超时),headless browser 可能不通需走 cron |
|
||||
| **共享客户端** | `~/.hermes/scripts/flairgs_client.py` |
|
||||
| **Auth Header** | `X-API-Key: 8Ax4TyZc66qrZgXK` |
|
||||
| **源码** | GS3: `/home/admin9webs/flairgs_v3_server.py` |
|
||||
| **VL 模型** | GS3: qwen3-vl:8b (127.0.0.1:11434) + GS4: qwen3-vl:8b (192.168.9.129:11434) |
|
||||
| **Chat 模型** | GS1-35B + 本机 4B |
|
||||
| **典型延迟** | ~7.5s (VL analyze) |
|
||||
|
||||
## 端点
|
||||
|
||||
### GET /health
|
||||
|
||||
```bash
|
||||
curl -H "X-API-Key: 8Ax4TyZc66qrZgXK" http://frp.9webs.online:17870/health
|
||||
```
|
||||
返回: `{"status": "ready", ...}`
|
||||
|
||||
### POST /api/analyze — VL 图像分析(最常用)
|
||||
|
||||
OCR、视觉问答、场景理解。**这是最主要端点。**
|
||||
|
||||
> ⚠️ **致命陷阱:JSON 字段是 `"base64"`,不是 `"image_base64"`!**
|
||||
> 用 `image_base64` 会静默失败——HTTP 200 但返回 `"No image data"`。
|
||||
|
||||
```
|
||||
POST /api/analyze
|
||||
Content-Type: application/json
|
||||
X-API-Key: 8Ax4TyZc66qrZgXK
|
||||
|
||||
{
|
||||
"base64": "<纯base64,不带 data:image/png;base64, 前缀>",
|
||||
"prompt": "可选,引导分析的文本提示",
|
||||
"filename": "可选,默认 image.jpg"
|
||||
}
|
||||
```
|
||||
|
||||
返回:
|
||||
```json
|
||||
{
|
||||
"description": "分析结果(中文/英文)...",
|
||||
"model": "GS3/qwen3-vl:8b",
|
||||
"elapsed": 7.8
|
||||
}
|
||||
```
|
||||
|
||||
**常用 prompt 模板:**
|
||||
- **OCR**: `"只输出图片中的文字,不要任何其他内容。"`
|
||||
- **验证码**: `"只输出验证码中的4个字符,不要任何其他内容。区分大小写。"`
|
||||
- **UI 分析**: `"Describe all visible buttons and their positions."`
|
||||
- **产品分析**: `"描述这个商品图片。类目?材质?风格?"`
|
||||
- **IP 筛查**: `"图中是否包含卡通/动漫/品牌角色?列出所有 IP 风险。"`
|
||||
|
||||
### POST /api/resize — 图片缩放
|
||||
|
||||
```json
|
||||
{"base64": "...", "width": 800, "height": 600}
|
||||
```
|
||||
|
||||
### POST /api/txt2img — 文生图
|
||||
|
||||
```json
|
||||
{"base64": "...", "prompt": "生成提示词"}
|
||||
```
|
||||
|
||||
### POST /api/white-bg — 白底处理(商品图标准化)
|
||||
|
||||
```json
|
||||
{"base64": "..."}
|
||||
```
|
||||
|
||||
### POST /api/chat — LLM 对话
|
||||
|
||||
GS1-35B + 本机 4B 混合推理。
|
||||
|
||||
## 共享客户端 (推荐)
|
||||
|
||||
所有脚本统一用 `~/.hermes/scripts/flairgs_client.py`,自动实现 LAN 优先 + FRP 回退:
|
||||
|
||||
```python
|
||||
from flairgs_client import analyze, health_check, get_active_url
|
||||
|
||||
# 自动 LAN → FRP 回退
|
||||
result = analyze(b64, "描述这张图片")
|
||||
print(result)
|
||||
|
||||
# 健康检查
|
||||
for url, info in health_check().items():
|
||||
print(f"{url} → {info['status']}")
|
||||
|
||||
# 获取当前活跃 URL
|
||||
active = get_active_url()
|
||||
```
|
||||
|
||||
**连接策略:**
|
||||
1. 先尝试 `http://192.168.9.105:7870`(LAN 直连,3s 超时)
|
||||
2. 失败自动回退 `http://43.160.244.125:17870`(FRP 隧道)
|
||||
3. 局域网用户享受低延迟直连,外网用户无缝 FRP
|
||||
|
||||
便捷封装也内置了:
|
||||
- `ocr_captcha(b64)` — 验证码 OCR
|
||||
- `describe_product(b64)` — 商品描述
|
||||
- `check_ip_risk(b64)` — IP 风险筛查
|
||||
|
||||
> 📖 实现细节、超时策略、扩展现有脚本的侵入模式:`references/client-internals.md`
|
||||
|
||||
## 标准集成模式:CDP 截图 → FlairGS 分析
|
||||
|
||||
最常见的组合:Cloud Bridge CDP 截图 + FlairGS VL 分析。使用共享客户端,自动 LAN/FRP 回退。
|
||||
|
||||
```python
|
||||
import json, urllib.request
|
||||
from flairgs_client import analyze as flairgs_analyze
|
||||
|
||||
# === Bridge 认证 ===
|
||||
BRIDGE_KEY = "Bing2026Cao$$$"
|
||||
BRIDGE_URL = "http://127.0.0.1:9228"
|
||||
SLOT = "desktop-mqjqto79"
|
||||
BH = {
|
||||
"Authorization": f"Bearer {BRIDGE_KEY}",
|
||||
"Content-Type": "application/json",
|
||||
"X-Desktop-Id": SLOT,
|
||||
}
|
||||
|
||||
def bridge_req(path, data=None):
|
||||
url = f"{BRIDGE_URL}{path}"
|
||||
body = json.dumps(data).encode() if data else None
|
||||
r = urllib.request.Request(url, data=body, method="POST")
|
||||
for k, v in BH.items(): r.add_header(k, v)
|
||||
return json.loads(urllib.request.urlopen(r, timeout=60).read())
|
||||
|
||||
# 1. Attach CDP
|
||||
att = bridge_req("/cdp/attach", {})
|
||||
tid = att["targetId"]
|
||||
att2 = bridge_req("/cdp/send", {
|
||||
"method": "Target.attachToTarget",
|
||||
"params": {"targetId": tid, "flatten": True}
|
||||
})
|
||||
sid = att2["result"]["sessionId"]
|
||||
|
||||
# 2. 截图
|
||||
ss = bridge_req("/cdp/send", {
|
||||
"method": "Page.captureScreenshot",
|
||||
"params": {"format": "png"},
|
||||
"sessionId": sid
|
||||
})
|
||||
b64 = ss["result"]["data"]
|
||||
|
||||
# 3. FlairGS 分析 (LAN → FRP 自动回退)
|
||||
desc = flairgs_analyze(b64, "描述这个页面上的所有按钮和功能")
|
||||
print(desc)
|
||||
```
|
||||
|
||||
## Desktop CDP 内网工具箱模式(2026-07 新增)
|
||||
|
||||
Bridge headless 浏览器到不了 192.168.9.x,但 Desktop CDP Chrome 可以(同内网):
|
||||
|
||||
```python
|
||||
# 1. Attach to Desktop CDP target
|
||||
att = bridge_req("/cdp/attach", {}) # X-Desktop-Id header
|
||||
|
||||
# 2. 交互工具箱页面 (evaluate 可用)
|
||||
ev("document.querySelector('textarea')['value']='prompt here'")
|
||||
ev("document.querySelectorAll('button')[7].click()") # "🎨 生成"
|
||||
|
||||
# 3. 提取生成的图片
|
||||
ev("var img=document.querySelectorAll('img')[1]; var c=document.createElement('canvas'); c.width=img.naturalWidth; c.height=img.naturalHeight; c.getContext('2d').drawImage(img,0,0); window.__CAPTURED_IMG=c.toDataURL('image/jpeg',0.9).split(',')[1]")
|
||||
|
||||
# 4. 图片在本地磁盘: /home/ubuntu/output/text2img_*.jpg
|
||||
```
|
||||
|
||||
**关键发现**:工具箱生成的图片存在 Bridge 服务器的 `/home/ubuntu/output/` 目录下,说明工具箱和 Bridge 共享文件系统。可直接用 Python 读取本地文件后调用 VL API。
|
||||
|
||||
```javascript
|
||||
(async function(){
|
||||
var urls = ["http://192.168.9.105:7870", "http://43.160.244.125:17870"];
|
||||
var key = "8Ax4TyZc66qrZgXK";
|
||||
var b64 = "iVBORw0KGgo..."; // 纯 base64
|
||||
// LAN first, FRP fallback
|
||||
for (var url of urls) {
|
||||
try {
|
||||
var r = await fetch(url + "/api/analyze", {
|
||||
method: "POST",
|
||||
headers: {"X-API-Key": key, "Content-Type": "application/json"},
|
||||
body: JSON.stringify({base64: b64, prompt: "你的问题"}),
|
||||
signal: AbortSignal.timeout(url.includes("105") ? 3000 : 120000)
|
||||
});
|
||||
return await r.text();
|
||||
} catch(e) { continue; }
|
||||
}
|
||||
return "All URLs failed";
|
||||
})()
|
||||
```
|
||||
|
||||
## Desktop CDP 交互模式(2026-07 实测)
|
||||
|
||||
Desktop CDP Chrome 在用户 Windows 机器上(192.168.9.x 内网),可直接操控工具箱 Web UI:
|
||||
|
||||
```javascript
|
||||
// 获取按钮列表(while 循环兼容 CDP 表达式过滤器)
|
||||
var b=document.querySelectorAll('button'); var i=b.length; var r=[];
|
||||
while(i--) { r.push(i+':'+b[i].innerText.substring(0,30)); }
|
||||
JSON.stringify(r)
|
||||
// → ["15:确认转换","14:取消",...,"7:🎨 生成",...,"1:🔍 一键分析","0:加载"]
|
||||
|
||||
// 填 textarea + 生成图
|
||||
var ta=document.querySelector('textarea');
|
||||
ta.value='prompt'; ta.dispatchEvent(new Event("input",{bubbles:true}));
|
||||
document.querySelectorAll('button')[7].click(); // 🎨 生成
|
||||
|
||||
// 一键 VL 分析
|
||||
document.querySelectorAll('button')[1].click(); // 🔍 一键分析
|
||||
|
||||
// 提取生成的图片为 base64
|
||||
var img=document.querySelectorAll('img')[1]; // 索引依页面而定
|
||||
var c=document.createElement('canvas');
|
||||
c.width=img.naturalWidth; c.height=img.naturalHeight;
|
||||
c.getContext('2d').drawImage(img,0,0);
|
||||
window.__CAPTURED_IMG=c.toDataURL('image/jpeg',0.9).split(',')[1];
|
||||
```
|
||||
|
||||
**限制**: CDP 仅支持 sync XHR(`XMLHttpRequest`,不可设 timeout),不支持 `fetch()`。VL API 跨域调用会失败(`NetworkError`),但工具箱页面内 `/api/analyze` 同源调用可用——`lastDescription` 全局变量存储分析结果。
|
||||
|
||||
## 陷阱大全
|
||||
|
||||
1. ⚠️ **字段名是 `base64`,不是 `image_base64`** — 用错静默返回 `"No image data"` HTTP 200。`~/.hermes/scripts/` 里多个脚本仍有此 bug。
|
||||
|
||||
2. **Base64 必须纯的** — 去掉 `data:image/png;base64,` 前缀。用 `dataUrl.split(",")[1]` 剥离。
|
||||
|
||||
3. **Multipart/form-data 会导致 500 crash** — 必须用 `Content-Type: application/json` + JSON body。
|
||||
|
||||
4. **`/api/load-url` 不可靠** — 可能超时。推荐本地下载后传 base64。
|
||||
|
||||
5. **frp 隧道可能截断大 payload** — 如果 SG5 突然报错但 GS3 本地正常,检查 frp。
|
||||
|
||||
6. **timeout** — 默认 60s 足够(VL ~7.5s)。大图或复杂 prompt 设 timeout=120。
|
||||
|
||||
7. **CORS** — 浏览器跨域会阻止。用服务端 Python 调用。
|
||||
|
||||
8. 🔴 **图片最小 32×32 像素** — qwen3-vl:8b 要求图片 ≥ 32×32,否则 Ollama 直接 500 panic。调用前校验尺寸!典型错误日志:`height:1 or width:1 < factor:32`。
|
||||
|
||||
9. 🔴 **VL 后端 503(2026-07)** — FRP 转发成功但 `127.0.0.1:7870/api/analyze` 返回 `503 Service Unavailable`。表示 VL 服务器进程挂了或端口未监听。检查 192.168.9.105 上 `systemctl --user status flairgs.service`。
|
||||
|
||||
10. 🔴 **alicdn 图片需 PIL convert('RGB')(2026-07)** — 从 alicdn 下载的产品图片(800x800 WebP/JPEG)直接传 base64 会导致 qwen3-vl 返回空描述。必须先用 `Image.open().convert('RGB')` 转换再 `save(format='JPEG')`。小 PNG 测试图不受影响。
|
||||
|
||||
10. **`get_active_url()` 每次调用都做 health check`
|
||||
|
||||
9. **`get_active_url()` 每次调用都做 health check** — 开销小(3s 超时),但不要在热循环里调用。脚本里一般只在构造 URL 时调用一次。缓存策略见 `references/client-internals.md`。
|
||||
|
||||
## 服务器管理
|
||||
|
||||
- **API 端口**: 192.168.9.105: `7870`(LAN: `192.168.9.105:7870`, FRP: `43.160.244.125:17870`)
|
||||
- **重启**: GS3 上 `systemctl --user restart flairgs.service`
|
||||
- **日志**: `journalctl --user -u flairgs.service -f`
|
||||
- **本地测试**(GS3 绕过 frp):
|
||||
```bash
|
||||
curl -X POST http://localhost:7870/api/analyze \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-API-Key: 8Ax4TyZc66qrZgXK" \
|
||||
-d '{"base64":"你的base64"}'
|
||||
```
|
||||
- **LAN 测试**(局域网内其他机器):
|
||||
```bash
|
||||
curl -X POST http://192.168.9.105:7870/api/analyze \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-API-Key: 8Ax4TyZc66qrZgXK" \
|
||||
-d '{"base64":"你的base64"}'
|
||||
```
|
||||
|
||||
## 已有脚本参考
|
||||
|
||||
`~/.hermes/scripts/` 中的参考脚本(**全部已升级 LAN 优先路由**):
|
||||
|
||||
| 脚本 | 用途 | 字段名 | 路由 | 状态 |
|
||||
|------|------|--------|:--:|:--:|
|
||||
| `dxm_try2.py` | 验证码 OCR + CDP 登录 | `base64` | `_fga_url()` | ✅ |
|
||||
| `dxm_captcha.py` | 验证码提取 + OCR | `base64` | `_fga_url()` | ✅ |
|
||||
| `dxm_ss.py` | CDP 截图 + 分析 | `base64` | `_fga_url()` | ✅ |
|
||||
| `flairgs_client.py` | **共享客户端(本模块)** | `base64` | 内置 LAN/FRP | ✅ |
|
||||
| `flairgs_v2.py` | CDP 截图 + VL 分析 + 重试 | `base64` | `_fga_url()` | ✅ |
|
||||
| `flairgs_full.py` | 163 邮箱截图 + 分析 | `base64` | `_fga_url()` | ✅ |
|
||||
| `flairgs_analyze.py` | 独立 FlairGS 分析 | `base64` | `_fga_url()` | ✅ |
|
||||
| `flairgs_163.py` | 163 工具栏分析 | `base64` | `_fga_url()` | ✅ |
|
||||
| `flairgs_test.py` | 多格式探测 | `base64` | `_fga_url()` | ✅ |
|
||||
| `flairgs_img.py` | 直接图片分析 | `base64` | `_fga_url()` | ✅ |
|
||||
| `flairgs_diag.py` | 诊断探测 | `base64` | `_fga_url()` | ✅ |
|
||||
| `flairgs_raw.py` | 多格式实验 | `base64` | `_fga_url()` | ✅ |
|
||||
| `flairgs_multi.py` | 多格式提交 | `base64` | `_fga_url()` | ✅ |
|
||||
| `flairgs_inbox.py` | 收件箱截图分析 | `base64` | `_fga_url()` | ✅ |
|
||||
| `flairgs_inbox2.py` | 收件箱截图分析 v2 | `base64` | `_fga_url()` | ✅ |
|
||||
| `flairgs_prompt.py` | Prompt 变体测试 | `base64` | `_fga_url()` | ✅ |
|
||||
| `flairgs_final.py` | 最终集成方案 | `base64` | `_fga_url()` | ✅ |
|
||||
| `flairgs_form.py` | 多表单格式 | `base64` | `_fga_url()` | ✅ |
|
||||
| `flairgs_go.py` | 快速调用 | `base64` | `_fga_url()` | ✅ |
|
||||
| `flairgs_oa.py` | OA 集成 | `base64` | `_fga_url()` | ✅ |
|
||||
| `flairgs_q.py` | 快速分析 | `base64` | `_fga_url()` | ✅ |
|
||||
| `flairgs_ollama.py` | Ollama 直连测试 | `base64` | `_fga_url()` | ✅ |
|
||||
|
||||
> **Legacy 模式说明**: 现有脚本使用 `from flairgs_client import get_active_url as _fga_url` + `_fga_url() + "/api/analyze"` 模式——这是最小侵入式改动,保留了原有请求构建逻辑。新脚本推荐直接用 `analyze(b64, prompt)`。
|
||||
Reference in New Issue
Block a user