"""知识库对话 Tool(T21-5 · FLOW §2「milvus_tool:产品规则 RAG + source_refs」)。 与 core_tools / risk.chat_tools 同构的 Tool 定义层:由 tool_service.run_tool 经统一注册表分发。search_knowledge 是公开知识检索(产品手册/交易规则)—— **不做客户归属校验**(ToolSpec.skip_access_check=True): - 知识内容为公开产品信息,无客户数据,归属校验无对象; - 开放范围(customer + advisor,用户拍板 2026-09-07)由意图层控制: _INTENT_KEYWORDS 仅 customer/advisor 组配置 kb 关键词,risk/analyst 不命中 → Tool 不会被触发;即使被直接调用,返回的也只是公开知识。 func 签名对齐 runner 约定 ``func(customer_id, core_ro, risk_repo, **params)``; query 为唯一业务参数(tool_node 从 user_message 构造 tool_input 注入, 一期不来自 LLM 输出)。RAG 异常(EmbeddingError 等)原样上抛 → run_tool 统一转 TOOL_ERROR 留痕,不静默降级空结果(防 LLM 编造回答)。 """ from __future__ import annotations from typing import Any, Callable from app.service import rag_service # 对话 Tool 检索条数(与 rag_service.DEFAULT_TOP_K 一致;独立常量便于对话口径单独调整) KB_TOP_K = 3 def search_knowledge( query: str = "", customer_id: str = "", core_ro=None, risk_repo=None, context_window: str = "", ) -> dict[str, Any]: """产品知识检索(TopK chunks + 溯源清单)。 customer_id/core_ro/risk_repo 为 runner 恒传参数,本 Tool 不使用 (公开知识,无归属语义);保留形参以满足统一签名。 context_window 为 tool_node 注入的近期对话,拼接进 query 使检索 感知历史(解决「那申购呢」类省略指代)。 """ effective_query = query if context_window and context_window.strip(): effective_query = f"{context_window.strip()}\n{query}" out = rag_service.search_knowledge(effective_query, top_k=KB_TOP_K) return { "hit_count": len(out["results"]), "results": out["results"], "source_refs": out["source_refs"], } class KBToolSpec(dict): """知识库 Tool 注册表条目(与 core_tools.ToolSpec / RiskToolSpec 同构; 新增 skip_access_check:True → run_tool 跳过客户归属校验)。""" KB_TOOL_REGISTRY: dict[str, KBToolSpec] = { "search_knowledge": KBToolSpec( func=search_knowledge, description="检索产品知识库(基金产品手册、费率、申赎规则、风险说明;返回结果附溯源)", requires_customer=False, param_whitelist=("query",), int_bounds={}, skip_access_check=True, ), } def get_kb_tool(name: str) -> KBToolSpec | None: """白名单查找(未知 Tool 返回 None)。""" return KB_TOOL_REGISTRY.get(name) def kb_tool_func(name: str) -> Callable[..., dict[str, Any]] | None: spec = KB_TOOL_REGISTRY.get(name) return spec["func"] if spec else None