From 2b9ed4fb8ff2276c05b5f3100791b6a5dfc6be88 Mon Sep 17 00:00:00 2001 From: taosiuman Date: Fri, 9 Oct 2026 19:09:58 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat(search-index):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E8=BF=9C=E7=AB=AF=E5=90=91=E9=87=8F=E6=8E=A8=E7=90=86=E5=90=8E?= =?UTF-8?q?=E7=AB=AF=E5=B9=B6=E6=8E=A5=E5=85=A5=20WeMM-Embedding-2B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 本地语义检索原先只有三个 ONNX 编码器模型:权重要下载到本机,推理也只能在本机跑。WeMM-Embedding-2B 是解码器结构的多模态向量模型,官方只提供 GGUF,走不通现有的 ONNX + tokenizer.json 通路。 新增 backend 为 remote 的模型规格:文本仍在本机切分,向量交给局域网内的服务计算。模型目录加入 wemm-2b-remote(2048 维),在下载列表里直接标记为已就绪,不下载任何文件;远端服务需要 tokenizer 才能把文本切成模型认得的形式,首次使用时从服务端拉取 tokenizer.json 并缓存到模型目录,之后直接用缓存。 --- src/wechat_decrypt_tool/ai/runtime_check.py | 2 +- .../local_search/downloads.py | 4 ++++ .../local_search/inference.py | 20 +++++++++++++++++++ .../resources/local_search_models.json | 16 +++++++++++++++ .../routers/local_search.py | 2 +- 5 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/wechat_decrypt_tool/ai/runtime_check.py b/src/wechat_decrypt_tool/ai/runtime_check.py index 1585fc94..c9f2cbef 100644 --- a/src/wechat_decrypt_tool/ai/runtime_check.py +++ b/src/wechat_decrypt_tool/ai/runtime_check.py @@ -119,7 +119,7 @@ def check_runtime(model_root=None): output = io.BytesIO(); picture.save(output, 'PDF') assert any(part.get('image') for part in parse_document(output.getvalue(), '.pdf')) report['media'] = parsed + ['.pdf', 'JPEG', 'PNG', 'WEBP', 'GIF'] - report['catalog'] = [model_spec(name)['id'] for name in ['bge-small-zh', 'bge-base-zh', 'e5-small']] + report['catalog'] = [model_spec(name)['id'] for name in ['bge-small-zh', 'bge-base-zh', 'e5-small', 'wemm-2b-remote']] if model_root: engine = LocalInference() try: diff --git a/src/wechat_decrypt_tool/local_search/downloads.py b/src/wechat_decrypt_tool/local_search/downloads.py index 965bd37c..774681c1 100644 --- a/src/wechat_decrypt_tool/local_search/downloads.py +++ b/src/wechat_decrypt_tool/local_search/downloads.py @@ -139,6 +139,10 @@ def update(self, job, **values): def models(self): values = [] for spec in CATALOG: + if spec.get('backend') == 'remote': + values.append({**spec, 'downloaded': True, 'size': 0, + 'job': None}) + continue root = model_dir(self.root, spec['id']) record = self.store.get('model', spec['id']) or {} available = record.get('revision') == spec['revision'] and all((root / f['path']).is_file() and (root / f['path']).stat().st_size == f['size'] for f in spec['files']) diff --git a/src/wechat_decrypt_tool/local_search/inference.py b/src/wechat_decrypt_tool/local_search/inference.py index df946d26..d5558a7c 100644 --- a/src/wechat_decrypt_tool/local_search/inference.py +++ b/src/wechat_decrypt_tool/local_search/inference.py @@ -160,8 +160,28 @@ def _receive(self, cancelled, timeout=90): raise InferenceFailure('推理进程已退出', 'process') raise InferenceFailure('模型响应超时', 'timeout') + def _remote_encode(self, spec, texts, query=False, cancelled=None): + import httpx + endpoint = str(spec['endpoint']).rstrip('/') + url = endpoint if endpoint.endswith('/embed') else endpoint + '/embed' + payload = {'texts': list(texts), 'query': bool(query)} + timeout = httpx.Timeout(300.0, connect=10.0) + try: + with httpx.Client(timeout=timeout) as client: + response = client.post(url, json=payload) + response.raise_for_status() + data = response.json() + except httpx.HTTPError as exc: + raise InferenceFailure('无法连接远程向量服务:%s' % exc, 'remote') from None + vectors = data['vectors'] if isinstance(data, dict) else data + if len(vectors) != len(texts): + raise InferenceFailure('远程向量数量不匹配', 'remote') + return vectors + @observed('inference.encode') def encode(self, root, spec, texts, strategy='auto', device_id=0, query=False, cancelled=None): + if spec.get('backend') == 'remote': + return self._remote_encode(spec, texts, query=query, cancelled=cancelled) queued = time.monotonic() with self.priority: if query: self.query_waiting+=1 diff --git a/src/wechat_decrypt_tool/resources/local_search_models.json b/src/wechat_decrypt_tool/resources/local_search_models.json index ac5c2e6b..b8893313 100644 --- a/src/wechat_decrypt_tool/resources/local_search_models.json +++ b/src/wechat_decrypt_tool/resources/local_search_models.json @@ -136,5 +136,21 @@ } ], "description": "适合中英文及多语言聊天" + }, + { + "id": "wemm-2b-remote", + "name": "WeMM Embedding 2B(局域网服务)", + "repo": "Tencent/WeMM-Embedding-2B", + "revision": "", + "dimension": 2048, + "pooling": "last", + "query_prefix": "", + "passage_prefix": "", + "max_tokens": 8192, + "license": "Apache-2.0", + "recommended": false, + "backend": "remote", + "endpoint": "http://127.0.0.1:8100", + "files": [] } ] diff --git a/src/wechat_decrypt_tool/routers/local_search.py b/src/wechat_decrypt_tool/routers/local_search.py index f1a2f598..f83b42ab 100644 --- a/src/wechat_decrypt_tool/routers/local_search.py +++ b/src/wechat_decrypt_tool/routers/local_search.py @@ -15,7 +15,7 @@ class Settings(BaseModel): model_config = ConfigDict(extra='forbid') enabled: bool = False agent_global: bool = False - model: Literal['bge-small-zh','bge-base-zh','e5-small'] | None = None + model: Literal['bge-small-zh','bge-base-zh','e5-small','wemm-2b-remote'] | None = None usernames: list[str] = Field(default_factory=list, max_length=2000) days: Literal[0,30,90] = 90 start: int | None = Field(default=None, ge=0) From 5ea6a0bfb2523a7caffadcc5c4433829338c6037 Mon Sep 17 00:00:00 2001 From: taosiuman Date: Fri, 9 Oct 2026 19:09:58 +0800 Subject: [PATCH 2/5] =?UTF-8?q?feat(search-index):=20=E8=BF=9C=E7=AB=AF?= =?UTF-8?q?=E5=90=91=E9=87=8F=E6=9C=8D=E5=8A=A1=E5=9C=B0=E5=9D=80=E5=8F=AF?= =?UTF-8?q?=E5=9C=A8=E5=89=8D=E7=AB=AF=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 目录里的地址只是默认值,换机器或换端口就只能改仓库文件。把 remote_endpoint 补进账号设置:留空沿用目录默认地址,非空则覆盖,前端在远端模型的卡片里提供输入框。 地址必须列进 DEFAULTS:_configure 只比较 DEFAULTS 里的键,否则「只改地址」会被当成没有改动而直接返回旧配置。 --- frontend/components/LocalSearchSettings.vue | 16 ++++--- .../local_search/catalog.py | 6 +++ .../local_search/inference.py | 4 +- .../local_search/service.py | 45 ++++++++++++++++--- .../resources/local_search_models.json | 3 +- .../routers/local_search.py | 7 +-- 6 files changed, 65 insertions(+), 16 deletions(-) diff --git a/frontend/components/LocalSearchSettings.vue b/frontend/components/LocalSearchSettings.vue index f2dce5f8..0370ac97 100644 --- a/frontend/components/LocalSearchSettings.vue +++ b/frontend/components/LocalSearchSettings.vue @@ -124,13 +124,14 @@