阿里雲 Milvus 知識庫可以把分散在各部門的制度、流程與 SOP 建成統一的內部問答入口,員工用自然語言提問即可拿到流程步驟與原文依據,並通過部門標籤把檢索範圍限制在指定範圍內。
方案說明
整體鏈路與通過阿里雲Milvus知識庫搭建智能客服問答應用完全一致:控制台定義標籤 → 按標籤大量匯入資料 → 發布版本 → SDK 檢索(可按標籤過濾)→ 大模型產生帶來源標註的回答 → Flask 提供問答頁面。工程代碼(kb_client.py、upload.py、app.py、templates/index.html、start.sh)請直接複用該文,本文只說明制度情境需要改動的部分:部門標籤體系、檢索參數、制度助手提示詞,以及制度版本管理。
建議第一版只選擇一個部門的 5~20 篇資料,驗證後再擴大範圍。整體耗時約 20~30 分鐘。
前提條件
已建立知識庫並記錄知識庫 ID(形如
kd-803ae9b10cc31)。已使用主帳號建立 RAM 使用者、勾選使用永久 AccessKey 訪問,並授予系統策略
AliyunMilvusFullAccess。已準備支援 OpenAI
chat/completions協議的大模型地址與 API Key。本地已安裝 Python 3.8 或以上版本,並已按上文教程建好工程目錄。
步驟一:定義部門與制度標籤
在知識庫詳情頁的基本資料下方單擊標籤 > 管理,添加以下三個標籤(類型均為 string):
標籤名 | 說明 | 樣本值 |
department | 制度歸屬部門 | 財務部、行政部、IT部 |
docType | 資料類型 | 制度、流程 |
effectiveDate | 生效日期 | 2026-01-01 |
標籤值支援中文,可直接使用部門與制度類型的中文名稱。
標籤管理對話方塊是整體儲存的。對話方塊開啟後標籤列表為非同步載入,請等到已有標籤全部顯示出來,再添加新標籤並單擊完成;否則可能以"空列表 + 新標籤"整體覆蓋,導致已有標籤定義被清除。已寫入資料的標籤值不會因此丟失,但建議補加標籤後核對詳情頁的標籤數量。
標籤定義主要用於統一取值口徑。未定義的標籤名也可以隨資料寫入並用於過濾,但仍建議先定義再匯入,便於團隊協作與後續維護。
步驟二:整理制度資料與匯入清單
按部門整理資料,放入
documents/目錄。檔案名稱建議包含制度名稱與版本或生效日期,便於在回答的來源區中識別。建立
documents.jsonl,為每篇資料標註部門、類型與生效日期。{"path": "documents/finance-travel.md", "metadata": {"department": "財務部", "docType": "制度", "effectiveDate": "2026-01-01"}} {"path": "documents/finance-reimbursement.md", "metadata": {"department": "財務部", "docType": "流程", "effectiveDate": "2026-02-01"}} {"path": "documents/hr-leave.md", "metadata": {"department": "行政部", "docType": "制度", "effectiveDate": "2026-01-01"}} {"path": "documents/it-troubleshoot.md", "metadata": {"department": "IT部", "docType": "流程", "effectiveDate": "2026-03-01"}}說明制度類資料建議在本文開頭寫明生效日期與制度負責人,並在舊版本本文中標註"已被 X 版替代"。大模型預設只能看到檢索片段的本文內容,
effectiveDate標籤不會自動進入提示詞,需要按步驟四改寫後才會帶上。制度資料多為短條目,可在知識庫詳情頁的處理策略中單擊建立策略調整切片粒度。最大分段長度的單位是字元(預設 512),建議設定為 580~770 字元(約相當於 384~512 tokens),盡量讓一個流程步驟保持完整。
步驟三:配置檢索參數與提示詞
在 config.json 中按制度情境調整 retrieval 與 scenario。
{
"retrieval": {
"page_size": 6,
"candidate_count": 48,
"min_score": 0.35,
"semantic_weight": 0.6,
"enable_query_expansion": true,
"rerank_model_name": "",
"tag_filter": {
"relation": "and",
"conditions": []
}
},
"scenario": {
"title": "企業制度與流程問答",
"system_prompt": "你是企業內部制度助手。只根據檢索到的發行制度回答;把流程整理成步驟,註明適用條件和所需材料,並標註[來源N];資料衝突或不足時明確提示聯絡制度負責人。",
"image_enabled": false,
"sample_questions": [
"差旅報銷需要提交哪些材料?",
"請假超過三天需要誰審批?",
"電腦無法連網時應該走什麼報障流程?"
]
}
}參數說明:
min_score:沒有跨語料通用的推薦值,必須用真實問題按自己的語料校準。推薦做法是先設min_score=0取回一批結果並人工標註相關性,再按召回與誤召回的分布選閾值;更換語料、切換重排模型或調整semantic_weight後都需要重新校準。本文語料實測:取 0.2 時與問題完全不相關的制度(0.36~0.43 分)也會進入大模型上下文,既增加開銷也提高誤答風險;調到 0.35 後這些結果被過濾掉。semantic_weight=0.6:制度提問常混合專有名詞與口語表達,語義與關鍵詞兼顧。該參數是最終分數的加權係數,計算方式為
score ≈ (1-semantic_weight) × keywordScore + semantic_weight × semanticScore(另可能疊加 rank feature),min_score在這個最終分數上做後置過濾。未啟用重排時
semanticScore是向量相似性,啟用重排後是重排模型分數,兩者量綱不同,因此調整權重或開關重排後必須結合 scoreDetails 重新校準閾值;調低權重並不必然壓低總分,只有當本批 semanticScore 高於 keywordScore 時才會下降。tag_filter.conditions預設留空,即檢索全部部門。僅當希望把某個入口限定在單一部門時才配置過濾條件,詳見步驟五。
實際可用的運算子只有 =、in、not in 三個。其餘運算子雖然出現在 400 Unsupported tag filter operator 報錯列出的 "Supported operators" 清單中,但實測傳入後條件會被靜默忽略、返回未經過濾的全部資料。其中 contains、not contains 只是集合運算 in、not in 的別名,並不是字串包含,按字串片段傳值只會返回 0 條。因此:需要按數值或日期區間篩選時請改用 in 枚舉取值;需要判斷標籤為空白時請改用 = "";配置好過濾條件後,務必與不加過濾的結果對比總條數,確認過濾確實生效。另外,如果 field 寫成未定義的標籤名,介面同樣不報錯,只會返回 0 條;把 op 寫成 eq、==、equal、like 等別名會返回 400 Unsupported tag filter operator。
步驟四:讓回答帶上部門與生效日期
制度問答需要判斷"哪一版有效、適用於哪個部門",因此要把標籤值一併拼進大模型上下文。檢索結果中的 tags 欄位不會返回你寫入的標籤值:該欄位取自服務內部的 chunk 標籤增強特徵,與 AddDocuments 寫入的 MetaFields 是兩個不同的欄位,也沒有請求參數可以開啟返回,因此它為空白不代表上傳失敗或標籤丟失。標籤值需要在用戶端自我維護映射:按檢索結果的 documentId 關聯本地匯入清單或業務側中繼資料後,再拼進大模型上下文。
在 app.py 中 app = Flask(__name__) 之後加入映射:
META_BY_NAME: dict[str, dict[str, Any]] = {}
_manifest = Path("documents.jsonl")
if _manifest.is_file():
for _line in _manifest.read_text(encoding="utf-8").splitlines():
if _line.strip():
_entry = json.loads(_line)
META_BY_NAME[Path(str(_entry["path"])).name] = _entry.get("metadata") or {}再改寫 llm_answer(),在拼接上下文時帶上標籤值,並在提問中告知當前日期:
def llm_answer(question: str, results: list[dict[str, Any]]) -> str:
if not LLM.get("enabled", True):
return "大模型未啟用;請查看下方檢索結果。"
if not results:
return "現有制度資料中沒有相關規定,請聯絡對應制度負責人確認。"
blocks = []
for index, item in enumerate(results, 1):
title = field(item, "documentName", "DocumentName")
meta = META_BY_NAME.get(title) or {}
label = "、".join(f"{key}={value}" for key, value in meta.items())
header = f"[來源{index}] {title}" + (f"({label})" if label else "")
blocks.append(f"{header}\n{field(item, 'content', 'Content')}")
context = "\n\n".join(blocks)
today = datetime.date.today().isoformat()
url = str(LLM["base_url"]).rstrip("/") + "/chat/completions"
response = requests.post(
url,
headers={"Authorization": f"Bearer {LLM['api_key']}"},
json={
"model": LLM["model"],
"temperature": 0.1,
"messages": [
{"role": "system", "content": SCENARIO.get("system_prompt", "只依據資料回答。")},
{"role": "user", "content": f"當前日期:{today}\n問題:{question}\n\n檢索資料:\n{context}"},
],
},
timeout=90,
)
response.raise_for_status()
return response.json()["choices"][0]["message"]["content"].strip()檔案頭部需補充 import datetime。
告知當前日期這一步不能省略。大模型不知道今天是哪一天,只給出 effectiveDate 時它會自行假設當前日期,可能得出與實際相反的結論——例如把已經生效的新版本判定為"尚未生效",轉而引用已廢止的舊標準。同時提供標籤值與當前日期後,模型才能正確選出現行版本並說明舊版本已被替代。
步驟五:按部門限定檢索範圍
需要為某個部門單獨提供入口時,配置對應過濾條件:
"tag_filter": {
"relation": "and",
"conditions": [
{"field": "department", "op": "=", "value": "財務部"}
]
}也可以組合多個條件,例如只查財務部的流程類資料:
"conditions": [
{"field": "department", "op": "=", "value": "財務部"},
{"field": "docType", "op": "=", "value": "流程"}
]一旦配置了部門過濾,該入口就只能回答該部門的問題。此時必須同步調整 sample_questions,否則員工提問其他部門的事項只會得到"沒有相關規定"。切換部門時需要同時修改三處:documents.jsonl 的 metadata、config.json 的 tag_filter、以及樣本問題。
步驟六:上傳、發布與驗證
上傳資料(
MetaFields對整批生效,指令碼會先按標籤分組再分批提交)。python upload.py --manifest documents.jsonl到控制台資料管理頁確認資料狀態為處理完成、標籤列顯示寫入的標籤(如
docType=流程, department=IT部 +1)。在版本管理頁單擊發布版本,完成三步嚮導後確認新版本狀態為發行。
重要預設同時最多隻能存在 3 個發行版本,該上限按租戶計算,不隨執行個體 CU 規格提升。達到上限後發布版本按鈕會置灰,此時頁面仍會顯示"當前有 N 條待發布變更",需要在版本記錄中刪除不再需要的舊版本後才能繼續發布。上限可由服務端調整,但目前沒有面向使用者的自助配額申請入口,需要提工單評估。版本刪除不可恢複,刪除後該版本立即不可檢索(後台再非同步清理資料),因此刪除前必須先確認沒有應用鎖定該版本號碼,並把仍在使用它的應用程式切換到新版本。
啟動服務並驗證。
python app.pycurl -sS http://127.0.0.1:7860/api/ask \ -H 'Content-Type: application/json' \ -d '{"question":"差旅報銷需要提交哪些材料?"}'
按以下四條驗收:
頁面能正常開啟,提交問題後同時返回答案與檢索來源。
回答中的
[來源N]能在下方來源區找到對應的制度切片。提問制度未覆蓋的事項時,回答明確說明沒有相關規定並提示聯絡制度負責人。
制度更新後重新發布版本,頁面能檢索到新版本內容。
制度情境注意事項
第一版只選一個部門,驗證檢索效果和提示詞後再擴大範圍。
舊版本制度的處理:如需保留歷史版本用於追溯,務必在本文標註"已被 X 版替代",並用
effectiveDate區分;如無追溯需求,建議在資料管理頁刪除舊版本後重新發布,避免模型在多版本間搖擺。制度更新後必須重新發布版本,應用使用
LATEST_PUBLISHED時才會檢索到新內容。版本配額只有 3 個:制度類知識庫更新頻繁,建議只保留"當前生效版本 + 最近一個歷史版本",發布新版本前先清理更早的版本。
同名檔案重複上傳會失敗:
doc_name_dedup=True時若整批檔案都因同名被去重,介面返回400 No OSS document can be registered.。更新制度時建議在檔案名稱中帶上版本號碼,或先在控制台刪除舊資料。關鍵制度問答建議保留人工兜底入口,並提示員工以正式發布的制度檔案為準。
常見問題
現象 | 原因與處理 |
提問返回 500,日誌顯示 |
|
加了標籤過濾但結果條數與不加過濾時完全一樣 | 使用了不生效的運算子(如 |
配置了部門過濾後,大部分問題都回答"沒有相關規定" | 該入口已被限定在單一部門。確認提問範圍與 |
按標籤過濾後總是 0 條 | 標籤名拼字與寫入時不一致(介面不會報錯,只返回 0 條)。在知識庫詳情頁 → 標籤 → 管理中核對標籤名。 |
回答混用了新舊兩版制度 | 檢索片段中缺少版本資訊。按步驟四把標籤值帶進上下文,並在舊版本本文標註已被替代。 |
上傳返回 | 整批檔案都因同名被去重。修改檔案名稱或先刪除舊資料。 |
檢索返回 | 尚未發布版本,或 |
發布版本按鈕置灰,但頁面顯示有待發布變更 | 發行版本已達 3 個上限(懸停按鈕可看到提示)。在版本記錄中刪除舊版本後即可發布。 |
想在回答中展示標籤,但檢索結果的 | 檢索結果當前不回填標籤值,按步驟四從 |
補加標籤後發現原有標籤定義消失 | 標籤管理對話方塊是整體儲存的。重新開啟對話方塊、等列表載入完成後補回缺失的標籤定義;已寫入資料的標籤值不受影響。 |