from __future__ import annotations
import concurrent.futures
import pytest
from conftest import (
http_get, http_post, find_list_of_vectors, find_vector, find_scalar_score, l2_norm,
M1_REPO,
)
DIM = 384
TEXTS = {
"en": "The quick brown fox jumps over the lazy dog.",
"zh": "今天天气非常好,适合出去爬山。",
"mixed": "使用 Rust 编写的高性能 embedding 服务 vecboost",
"emoji": "这个产品真不错 👍🔥 下次还来!",
}
def _embed(port, text):
return http_post(port, "/api/1/embed", {"text": text})
def test_r001_single_embed_en_zh_mixed_emoji(base_server):
port = base_server["port"]
vecs = {}
for name, text in TEXTS.items():
st, body = _embed(port, text)
assert st == 200, f"{name}: HTTP {st}: {str(body)[:200]}"
vec = find_vector(body)
assert vec, f"{name}: 响应中未找到向量: {str(body)[:200]}"
assert len(vec) == DIM, f"{name}: 维度 {len(vec)} != {DIM}"
assert any(abs(x) > 1e-6 for x in vec), f"{name}: 向量全零"
vecs[name] = vec
st, body = _embed(port, TEXTS["en"])
assert find_vector(body) == vecs["en"], "相同文本两次嵌入结果不一致(确定性失败)"
n = l2_norm(vecs["zh"])
assert abs(n - 1.0) < 0.05, f"L2 范数 {n} 偏离 1(归一化预期)"
def test_r002_batch_32_order_and_consistency(base_server):
port = base_server["port"]
batch = [f"批量测试文本 {i}:vecboost scenario {i}" for i in range(32)]
st, body = http_post(port, "/api/1/embed/batch", {"texts": batch})
assert st == 200, f"HTTP {st}: {str(body)[:200]}"
items = body.get("embeddings") if isinstance(body, dict) else None
assert isinstance(items, list) and len(items) == 32, f"数量异常: {len(items) if items else 0}"
embs = [it["embedding"] for it in items]
assert all(len(v) == DIM for v in embs), "批量中存在维度不一致"
st1, b1 = _embed(port, batch[0])
assert find_vector(b1) == embs[0], "批量首条与单句结果不一致"
def test_r003_similarity_same_pair_high_score(base_server):
port = base_server["port"]
st, body = http_post(port, "/api/1/similarity",
{"source": "I love Beijing", "target": "I love Beijing"})
assert st == 200, f"HTTP {st}: {str(body)[:200]}"
same = find_scalar_score(body)
assert same is not None, f"未找到分数: {str(body)[:200]}"
assert same > 0.99, f"相同文本相似度 {same} 应≈1.0"
st2, body2 = http_post(port, "/api/1/similarity",
{"source": "machine learning model training", "target": "today lunch is noodles"})
assert st2 == 200, f"HTTP {st2}"
diff = find_scalar_score(body2)
assert diff is not None and diff < same, f"无关文本相似度 {diff} 不应高于相同文本 {same}"
def test_r004_search_not_exposed(base_server):
port = base_server["port"]
st, _ = http_post(port, "/api/1/embed/search",
{"query": "可爱的猫咪", "texts": [f"文档{i}" for i in range(10)], "top_k": 3})
if st == 404:
pytest.skip("能力记录:embed/search 未暴露 HTTP 路由(服务层能力存在,记入报告)")
assert st == 200, f"HTTP {st}"
def test_r005_file_embed_txt(base_server):
port = base_server["port"]
from conftest import write_file
content = "\n\n".join(
f"第 {i} 段:vecboost 支持文件级嵌入,自动分块并保持重叠语义。section {i}。" for i in range(6)
)
path = write_file("base", "files/sample.txt", content)
st, body = http_post(port, "/api/1/embed/file", {"path": path})
assert st == 200, f"HTTP {st}: {str(body)[:300]}"
stats = body.get("stats", {}) if isinstance(body, dict) else {}
chunks = stats.get("total_chunks") or 0
vec = find_vector(body)
assert chunks >= 2, f"分块数异常: {str(body)[:200]}"
assert vec and len(vec) == DIM, f"聚合向量维度异常: {len(vec) if vec else None}"
def test_r006_matryoshka_via_openai_dimensions(base_server):
port = base_server["port"]
for d in (128, 256):
st, body = http_post(port, "/v1/embeddings",
{"input": "matryoshka 降维测试", "model": M1_REPO,
"dimensions": d, "encoding_format": "float"})
assert st == 200, f"dimensions={d}: HTTP {st}: {str(body)[:200]}"
vec = find_vector(body)
assert vec and len(vec) == d, f"dimensions={d} 返回 {len(vec) if vec else None} 维"
assert abs(l2_norm(vec) - 1.0) < 0.05, f"dimensions={d} 未重新归一化: L2={l2_norm(vec)}"
for bad in (0, 4096):
st, body = http_post(port, "/v1/embeddings",
{"input": "x", "model": M1_REPO, "dimensions": bad})
assert st in (400, 422), f"非法 dimensions={bad} 返回 {st}(预期 400/422)"
def test_r007_openai_compat_endpoint(base_server):
port = base_server["port"]
st, body = http_post(port, "/v1/embeddings",
{"input": ["hello world", "你好世界"], "model": M1_REPO,
"encoding_format": "float"})
assert st == 200, f"HTTP {st}: {str(body)[:200]}"
data = body.get("data") if isinstance(body, dict) else None
assert isinstance(data, list) and len(data) == 2, f"OpenAI 批量结构异常: {str(body)[:200]}"
embs = [item.get("embedding") for item in data]
assert all(isinstance(e, list) and len(e) == DIM for e in embs), "embedding 结构异常"
st1, b1 = _embed(port, "hello world")
v_native = find_vector(b1)
dot = sum(a * b for a, b in zip(v_native, embs[0]))
na = sum(a * a for a in v_native) ** 0.5
nb = sum(b * b for b in embs[0]) ** 0.5
cos = dot / (na * nb)
assert cos > 0.999999, f"OpenAI 端点与原生端点向量语义不一致: cos={cos}"
def test_r008_repeat_request_cache_effect(base_server):
port = base_server["port"]
st1, b1 = _embed(port, "缓存命中测试文本 cache probe")
st2, b2 = _embed(port, "缓存命中测试文本 cache probe")
assert st1 == st2 == 200
assert find_vector(b1) == find_vector(b2), "缓存路径下向量漂移"
mst, metrics = http_get(port, "/metrics")
assert mst == 200, f"/metrics HTTP {mst}"
has_cache = "cache" in str(metrics).lower()
if not has_cache:
pytest.skip("能力记录:/metrics 中未发现 cache 相关指标(记入报告)")