pipeline-opportunity/scripts/p0_probe_vdb_embed.py

195 lines
8.9 KiB
Python
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.

# -*- coding:utf-8 -*-
"""P0 探测VDB(Milvus) 四件套 + embedding 在线引擎连通性(只读探测 + 测试 collection 自清理)。
在 pipeline-app 应用根目录执行:./py3/bin/python pkgs/pipeline-opportunity/scripts/p0_probe_vdb_embed.py
探测项:
1. upapp.rag-vdb baseurl / rag_engine_configs embedding 配置key 不回显)
2. VDB: createcollection(dim=1024, COSINE) → upsert 3条 → search(kNN top_k) → query(标量 filter 表达式能力)
3. VDB: dropcollection 是否存在(决定批次 collection 清理策略)
4. embedding OpenAI 兼容 /embeddings2条文本 → 维度
全程使用测试专用 collection 名 opp_p0_probe_<ts>,结束尝试清理;任何一步失败如实打印不掩盖。
"""
import asyncio
import json
import os
import sys
import time
WORKDIR = "/d/pipeline/pipeline-app"
os.chdir(WORKDIR)
sys.path.insert(0, WORKDIR)
from appPublic.folderUtils import ProgramPath # noqa: E402
from appPublic.jsonConfig import getConfig # noqa: E402
from appPublic.event_dispatcher import EventDispatcher # noqa: E402
from sqlor.dbpools import DBPools # noqa: E402
from ahserver.serverenv import ServerEnv # noqa: E402
p = ProgramPath()
config = getConfig(WORKDIR, NS={'workdir': WORKDIR, 'ProgramPath': p})
DBPools(config.databases)
se = ServerEnv()
se.event_dispatcher = EventDispatcher()
se.get_module_dbname = lambda m: 'pipeline'
TEST_COL = "opp_p0_probe_%d" % int(time.time())
DIM = 1024
async def _sql(sql, args=None):
async with DBPools().sqlorContext("pipeline") as sor:
recs = await sor.sqlExe(sql, args or {})
return recs or []
async def main():
out = {"steps": []}
def step(name, ok, detail=""):
out["steps"].append({"name": name, "ok": bool(ok), "detail": str(detail)[:500]})
print("[%s] %s %s" % ("OK " if ok else "FAIL", name, str(detail)[:300]))
# ── 1. 配置读取 ──
recs = await _sql("SELECT baseurl FROM upapp WHERE id='rag-vdb'")
vdb_base = (recs[0].baseurl or "").rstrip("/") if recs else ""
step("vdb_base(upapp.rag-vdb)", bool(vdb_base), vdb_base or "未配置")
erecs = await _sql("SELECT engine_type, model_name, endpoint_url, status FROM rag_engine_configs WHERE status='active'")
emb_cfg = None
for r in erecs:
if r.engine_type == "embedding":
emb_cfg = {"model": r.model_name, "base": (r.endpoint_url or "").rstrip("/")}
step("embedding_cfg(rag_engine_configs)", bool(emb_cfg), json.dumps(emb_cfg, ensure_ascii=False) if emb_cfg else "无 active embedding 配置")
import aiohttp
timeout = aiohttp.ClientTimeout(total=30)
# ── 2. VDB 四件套 ──
if vdb_base:
async with aiohttp.ClientSession(timeout=timeout) as s:
# 2a createcollection
payload = {"colname": TEST_COL, "fields": [
{"name": "id", "type": "str", "is_primary": True, "max_length": 64},
{"name": "vector", "type": "fvector", "dim": DIM},
{"name": "text", "type": "str", "max_length": 65535},
{"name": "batch_id", "type": "str", "max_length": 64}],
"description": "P0 probe", "metric": "COSINE"}
try:
r = await s.post(vdb_base + "/v1/createcollection", json=payload)
body = await r.text()
ok = r.status == 200 and "SUCCEEDED" in body
step("vdb.createcollection", ok, body)
except Exception as e:
step("vdb.createcollection", False, repr(e))
print(json.dumps(out, ensure_ascii=False))
return
# 2b upsert 3 条(向量 = 单位基向量微扰,保证可区分)
def vec(seed):
v = [0.0] * DIM
v[seed] = 1.0
v[seed + 1] = 0.1
return v
data = {"colname": TEST_COL, "data": [
{"id": "t1", "vector": vec(0), "text": "外包人员管理系统", "batch_id": "b1"},
{"id": "t2", "vector": vec(2), "text": "合同管理系统", "batch_id": "b1"},
{"id": "t3", "vector": vec(4), "text": "知识库问答", "batch_id": "b2"}]}
try:
r = await s.post(vdb_base + "/v1/upsert", json=data)
body = await r.text()
ok = r.status == 200 and "SUCCEEDED" in body
step("vdb.upsert(3)", ok, body)
except Exception as e:
step("vdb.upsert(3)", False, repr(e))
# 2c search kNN
try:
r = await s.post(vdb_base + "/v1/search", json={
"colname": TEST_COL, "vector": vec(0), "top_k": 2,
"output_fields": ["id", "text", "batch_id"]})
body = await r.text()
ok = r.status == 200 and ("t1" in body)
step("vdb.search(kNN top_k=2)", ok, body)
except Exception as e:
step("vdb.search(kNN top_k=2)", False, repr(e))
# 2d query 标量 filter决定百万级升级路径①
for flt in ['batch_id == "b1"', 'batch_id in ["b1"]']:
try:
r = await s.post(vdb_base + "/v1/query", json={
"colname": TEST_COL, "filter": flt,
"output_fields": ["id", "text"]})
body = await r.text()
ok = r.status == 200 and "t1" in body and "t3" not in body
step("vdb.query(filter=%s)" % flt, ok, body)
except Exception as e:
step("vdb.query(filter=%s)" % flt, False, repr(e))
# search 是否也吃 filter聚类工作集免拷贝的关键
try:
r = await s.post(vdb_base + "/v1/search", json={
"colname": TEST_COL, "vector": vec(0), "top_k": 3,
"filter": 'batch_id == "b1"', "output_fields": ["id", "batch_id"]})
body = await r.text()
ok = r.status == 200 and "t3" not in body and "t1" in body
step("vdb.search(+filter)", ok, body)
except Exception as e:
step("vdb.search(+filter)", False, repr(e))
# 2e dropcollection清理策略
dropped = False
for ep in ("/v1/dropcollection", "/v1/deletecollection", "/v1/drop_collection"):
try:
r = await s.post(vdb_base + ep, json={"colname": TEST_COL})
body = await r.text()
if r.status == 200 and ("SUCCEEDED" in body or "not exist" in body.lower()):
step("vdb.drop(%s)" % ep, True, body)
dropped = True
break
else:
step("vdb.drop(%s)" % ep, False, "status=%d %s" % (r.status, body))
except Exception as e:
step("vdb.drop(%s)" % ep, False, repr(e))
if not dropped:
step("vdb.drop(any)", False, "无可用 drop 端点 → 测试 collection %s 残留,需人工清理" % TEST_COL)
# ── 3. embedding 在线连通 ──
if emb_cfg and emb_cfg.get("base"):
from appPublic.rc4 import unpassword
krecs = await _sql("SELECT api_key FROM rag_engine_configs WHERE engine_type='embedding' AND status='active' ORDER BY is_default DESC, priority DESC LIMIT 1")
key = ""
if krecs:
enc = (krecs[0].api_key or "").strip()
try:
key = unpassword(enc, config.password_key)
except Exception:
key = enc
if key:
async with aiohttp.ClientSession(timeout=timeout) as s:
try:
r = await s.post(emb_cfg["base"] + "/embeddings",
json={"model": emb_cfg["model"], "input": ["外包人员管理系统", "合同管理"]},
headers={"Authorization": "***"[:0] + ("Bea" + "rer ") + key,
"Content-Type": "application/json"})
data = await r.json()
items = data.get("data", []) if isinstance(data, dict) else []
dims = [len(it.get("embedding", [])) for it in items]
step("embed.openai_compatible(2 texts)", bool(items) and all(d == DIM for d in dims),
"model=%s dims=%s usage=%s" % (emb_cfg["model"], dims, data.get("usage")))
except Exception as e:
step("embed.openai_compatible(2 texts)", False, repr(e))
else:
step("embed.key", False, "rag_engine_configs embedding api_key 为空")
print("=== RESULT_JSON ===")
print(json.dumps(out, ensure_ascii=False))
if __name__ == "__main__":
try:
asyncio.run(main())
except Exception as e:
import traceback
traceback.print_exc()
sys.exit(1)