"""搜索 DSPY — 接收 Form 参数,返回搜索结果 Widget""" query = (params_kw.get("query") or "").strip() kb_id = (params_kw.get("kb_id") or "").strip() top_k = int(params_kw.get("top_k") or 10) recall_k = int(params_kw.get("recall_k") or top_k * 3) env = request._run_ns userorgid = await env.get_userorgid() # Resolve KBs async with get_sor_context(env, 'rag') as sor: if kb_id: recs = await sor.R("knowledge_bases", {"id": kb_id}) kb_ids = [r.id for r in recs] else: sql = "SELECT id FROM knowledge_bases WHERE org_id IS NULL OR org_id=${org_id}$" recs = await sor.sqlExe(sql, {"org_id": userorgid}) kb_ids = [r.id for r in recs] if not kb_ids: result = {"widgettype": "Text", "options": {"text": "没有可搜索的知识库", "color": "#aaa", "halign": "center", "padding": "40px", "cfontsize": 14}} elif not query: result = {"widgettype": "Text", "options": {"text": "请输入搜索关键词", "color": "#aaa", "halign": "center", "padding": "40px", "cfontsize": 14}} else: # Build embedding from rag.init import _call_uapi try: emb_resp = await _call_uapi("rag-embedding", "embed", {"texts": [query], "model": "CLIP-ViT-H-14"}) query_vec = (emb_resp.get("embeddings", []) or [None])[0] if isinstance(emb_resp, dict) else None except Exception: query_vec = None if not query_vec: result = {"widgettype": "Text", "options": {"text": "向量化失败,请稍后重试", "color": "#e53e3e", "halign": "center", "padding": "40px", "cfontsize": 14}} else: # Multi-KB VDB search all_hits = [] for kid in kb_ids: try: vdb_resp = await _call_uapi("rag-vdb", "search", {"collection": kid, "vector": query_vec, "topK": recall_k}) data = vdb_resp if isinstance(data, dict): for key in ("results", "data", "hits", "rows"): if isinstance(data.get(key), list): data = data[key] break if isinstance(data, list): for item in data: if isinstance(item, dict): all_hits.append({"id": item.get("id", ""), "text": item.get("text", item.get("content", "")), "score": item.get("score", item.get("distance", 0)), "kb_id": kid}) except Exception: pass # Deduplicate + sort seen = set() unique_hits = [] for h in sorted(all_hits, key=lambda x: x.get("score", 0), reverse=True): hid = h.get("id", h.get("text", "")) if hid not in seen: seen.add(hid) unique_hits.append(h) # Rerank if query and unique_hits: docs = [h.get("text", "")[:500] for h in unique_hits[:recall_k]] try: rr = await _call_uapi("rag-reranker", "rerank", {"query": query, "documents": docs}) scores = rr.get("scores", rr.get("results", [])) if isinstance(rr, dict) else [] if isinstance(scores, list) and len(scores) == len(docs): for i, s in enumerate(scores): unique_hits[i]["rerank_score"] = s.get("score", 0) if isinstance(s, dict) else float(s or 0) unique_hits.sort(key=lambda x: x.get("rerank_score", 0), reverse=True) except Exception: pass final = unique_hits[:top_k] if not final: result = {"widgettype": "Text", "options": {"text": "未找到相关结果", "color": "#aaa", "halign": "center", "padding": "40px", "cfontsize": 14}} else: cards = [] for i, h in enumerate(final): text = h.get("text", "")[:300] score = h.get("rerank_score", h.get("score", 0)) score_pct = f"{min(abs(float(score)) * 100, 100):.0f}%" if score else "" cards.append({ "widgettype": "VBox", "options": {"cwidth": 100, "padding": "16px", "bgcolor": "#f8fafc", "border": "1px solid #e2e8f0", "borderRadius": "8px", "spacing": "8px"}, "subwidgets": [ {"widgettype": "HBox", "options": {"spacing": "8px"}, "subwidgets": [ {"widgettype": "Text", "options": {"text": f"#{i+1}", "cfontsize": 12, "color": "#3b82f6", "fontWeight": "bold", "padding": "2px 8px", "bgcolor": "#eff6ff", "borderRadius": "4px"}}, {"widgettype": "Text", "options": {"text": f"相关度: {score_pct}", "cfontsize": 11, "color": "#888"}}, {"widgettype": "Text", "options": {"text": f"KB: {h.get('kb_id','')[:12]}", "cfontsize": 11, "color": "#aaa"}} ]}, {"widgettype": "Text", "options": {"text": text, "cfontsize": 13, "color": "#333", "lineHeight": 1.6}}, {"widgettype": "Text", "options": {"text": h.get("id", "")[:40], "cfontsize": 10, "color": "#bbb"}} ] }) result = {"widgettype": "VBox", "options": {"width": "100%", "spacing": "12px"}, "subwidgets": [ {"widgettype": "Text", "options": {"text": f"找到 {len(final)} 条结果 (共召回 {len(all_hits)} 条)", "cfontsize": 13, "color": "#666", "padding": "0 0 8px 0"}}, *cards ]} result