Files
atomk-hermes-skills/skills/cross-border-ecommerce/flairgs-vision-api/SKILL.md
T

319 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 后端 5032026-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)`。