阿里云 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 个上限(悬停按钮可看到提示)。在版本记录中删除旧版本后即可发布。 |
想在回答中展示标签,但检索结果的 | 检索结果当前不回填标签值,按步骤四从 |
补加标签后发现原有标签定义消失 | 标签管理对话框是整体保存的。重新打开对话框、等列表加载完成后补回缺失的标签定义;已写入数据的标签值不受影响。 |