療知識圖譜問答機(jī)器人實踐指南)
簡介一套基于Python與Neo4j圖數(shù)據(jù)庫的醫(yī)療知識圖譜智能問答機(jī)器人畢業(yè)設(shè)計項目面向計算機(jī)相關(guān)專業(yè)正在準(zhǔn)備畢業(yè)設(shè)計的學(xué)生以及需要完整實戰(zhàn)練習(xí)的編程學(xué)習(xí)者也可直接用于課程設(shè)計或期末大作業(yè)。項目代碼經(jīng)過嚴(yán)格調(diào)試可正常運行。壓縮包共36個文件約15.34MB核心為8個Python源碼文件覆蓋問題分析、醫(yī)療圖譜構(gòu)建、查詢生成、答案檢索等完整問答流程另附10個txt說明文檔、7張圖片以及配套前端樣式與腳本方便對照理解與二次開發(fā)。當(dāng)前已有701人學(xué)習(xí)下載。代碼附帶超詳細(xì)中文注釋與使用說明重點模塊的圖譜構(gòu)建、問答主流程均清晰標(biāo)注處理邏輯適合參考其圖數(shù)據(jù)庫建模思路和意圖識別設(shè)計并可直接在此基礎(chǔ)上擴(kuò)展藥品、疾病、癥狀等知識節(jié)點。1. 醫(yī)療知識圖譜問答機(jī)器人先建圖再讓問句找到答案拿到這份 Python 基于 Neo4j 圖數(shù)據(jù)庫的醫(yī)療知識圖譜智能問答機(jī)器人源碼最容易犯的錯誤是一上來就研究問答代碼。把問答拆開看七成工作量在建圖三成在匹配真正跑通之后你會發(fā)現(xiàn)圖建對了問答就成功了一半。它解決的是醫(yī)療信息查詢里“回答可解釋、可溯源”的問題——比如患者問“糖尿病能不能吃西瓜”系統(tǒng)不是甩出一段搜索摘錄而是給出“糖尿病—禁忌食物—西瓜”這條關(guān)系路徑答案自帶依據(jù)。適合正在做知識圖譜練手或畢設(shè)、想從普通增刪改查轉(zhuǎn)向圖查詢的 Python 開發(fā)者也適合醫(yī)療信息化場景做技術(shù)驗證。下文按“建?!鷮?dǎo)入→問句解析→查詢生成→排錯→驗證”的順序把整條鏈路說透。2. 醫(yī)療知識圖譜建模與 Neo4j 落地先把圖建對再談問答建圖不是把數(shù)據(jù)塞進(jìn) Neo4j 就完事實體怎么分、關(guān)系朝哪個方向、別名怎么歸一直接決定后面問答代碼的復(fù)雜程度。順序反了后面每寫一個查詢模板都要給前面的設(shè)計買單這是新手最容易翻車的地方。2.1 實體與關(guān)系怎么定按“用戶會怎么問”取舍本體醫(yī)療知識圖譜本體不需要做到教科書級別夠用就好。常見的最小實體集是疾病 Disease、癥狀 Symptom、藥品 Medicine、科室 Department、檢查項目 CheckItem按業(yè)務(wù)需要再加食物 Food。每個實體至少帶 name唯一、alias_list別名列表、desc簡述三個屬性來源字段看場景加。關(guān)系按“問句里會出現(xiàn)的主語和賓語”來定義方向統(tǒng)一才能讓模板好寫(Disease)-[:HAS_SYMPTOM]-(Symptom)癥狀尋因和疾病查癥共用一條邊(Medicine)-[:TREAT]-(Disease)治什么病(Medicine)-[:CONTRAINDICATED]-(Disease)禁忌必須單獨建關(guān)系不能復(fù)用 TREAT 反向(Disease)-[:VISIT_DEPT]-(Department)掛什么科(Disease)-[:FORBID_FOOD]-(Food)忌口什么同義詞歸一化是醫(yī)療圖譜特有的前置工作?!疤悄虿 薄?型糖尿病”“消渴癥”在口語里可能指同一個病“拜糖平”和“阿卡波糖”是同一成分的不同商品名。常見做法是每個實體維護(hù)一個 alias 屬性導(dǎo)入時把別名合并到節(jié)點上問答層統(tǒng)一查標(biāo)準(zhǔn)名匹配層負(fù)責(zé)把口語映射過去。這里有一個注意點關(guān)系方向全篇保持一致不要“藥品治疾病”和“疾病被藥品治”混著建否則 4.1 的模板會非常難寫。屬性取舍上只導(dǎo)查詢和展示會用的字段別把整庫數(shù)據(jù)搬進(jìn)來。醫(yī)療數(shù)據(jù)源經(jīng)常有大段的說明文本真需要全文展示就放 desc 字段其他中間字段一律不建索引減少導(dǎo)入時間和存儲占用。2.2 數(shù)據(jù)導(dǎo)入的兩條路線LOAD CSV 與 Python 驅(qū)動小規(guī)模數(shù)據(jù)幾百到幾萬節(jié)點用 LOAD CSV 最省事在 Neo4j Browser 或 cypher-shell 里就能直接執(zhí)行。先給兩張表disease.csv 放節(jié)點字段relation.csv 放關(guān)系三元組。LOAD CSV WITH HEADERS FROM file:///disease.csv AS row MERGE (d:Disease {name: row.name}) SET d.alias row.alias, d.desc row.desc, d.source row.source; LOAD CSV WITH HEADERS FROM file:///relation.csv AS row MATCH (d:Disease {name: row.disease_name}) MATCH (s:Symptom {name: row.symptom_name}) MERGE (d)-[:HAS_SYMPTOM]-(s);邏輯說明LOAD CSV 是 Neo4j 內(nèi)置的批量導(dǎo)入命令WITH HEADERS 讓首行成為字段名MERGE 按 name 判重重復(fù)執(zhí)行不會堆出重復(fù)節(jié)點第二條語句先定位兩個端點再建關(guān)系避免用 CREATE 造成重復(fù)關(guān)系。文件默認(rèn)放在 Neo4j 的 import 目錄下file:/// 后面跟相對路徑即可。參數(shù)說明文件編碼必須 UTF-8Excel 另存的 CSV 大概率是 GBK直接導(dǎo)會亂碼見 5.2name 字段建議用醫(yī)療標(biāo)準(zhǔn)術(shù)語別名放 alias 列MERGE 依賴唯一約束最好在建表前先加約束否則數(shù)據(jù)量上去后判重性能會退化。要走定時更新或和爬蟲結(jié)果聯(lián)動就用 Python 官方驅(qū)動做自動化導(dǎo)入from neo4j import GraphDatabase URI bolt://localhost:7687 AUTH (neo4j, password) class KGImporter: def __init__(self): self.driver GraphDatabase.driver(URI, authAUTH) def close(self): self.driver.close() def make_relation(self, med_name, disease_name): with self.driver.session() as session: session.run( MATCH (m:Medicine {name: $med}) MATCH (d:Disease {name: $dis}) MERGE (m)-[:TREAT]-(d) , medmed_name, disdisease_name, ) if __name__ __main__: imp KGImporter() imp.make_relation(阿卡波糖, 2型糖尿病) imp.close()邏輯說明neo4j 驅(qū)動是官方庫用 $ 占位符傳參避免字符串拼接session 內(nèi)部自動管理事務(wù)每次 make_relation 做兩次匹配、一次 MERGE適合增量修正某條關(guān)系。參數(shù)說明URI 里的 7687 是 Bolt 協(xié)議默認(rèn)端口瀏覽器訪問的 HTTP 端口是 7474兩者別搞混AUTH 元組里是初始賬號密碼第一次啟動 Neo4j 時會要求修改如果入庫腳本跑得頻繁可以給 driver 加 max_connection_pool_size 限制連接數(shù)防止連接池占滿。2.3 建完圖先驗證三條查詢確認(rèn)結(jié)構(gòu)與質(zhì)量問答代碼還沒寫先把圖“驗貨”。下面三條 Cypher 從規(guī)模、路徑、空結(jié)果三個角度檢查。// 1. 統(tǒng)計各標(biāo)簽節(jié)點數(shù)量 MATCH (n) RETURN labels(n)[0] AS label, count(*) AS cnt ORDER BY cnt DESC; // 2. 抽查一條醫(yī)療路徑糖尿病的癥狀 MATCH (d:Disease {name: 糖尿病})-[:HAS_SYMPTOM]-(s:Symptom) RETURN s.name LIMIT 10; // 3. 查糖尿病的禁忌食物返回空不一定錯 MATCH (d:Disease {name: 糖尿病})-[:FORBID_FOOD]-(f:Food) RETURN f.name LIMIT 10;邏輯說明第一條確認(rèn)導(dǎo)入沒有漏表哪個標(biāo)簽數(shù)量異常一眼可見第二條驗證關(guān)系方向如果這里返回空多半是導(dǎo)入腳本里把 HAS_SYMPTOM 建反了第三條空返回有兩種可能圖譜沒這個關(guān)系或者問法本身在數(shù)據(jù)里就沒有答案先別急著改代碼。接下來建唯一約束防止后續(xù)導(dǎo)入腳本重復(fù)執(zhí)行時節(jié)點翻倍CREATE CONSTRAINT ON (d:Disease) ASSERT d.name IS UNIQUE;參數(shù)說明這個約束語法在 Neo4j 3.x 和部分早期 4.x 版本可用新版改成了 CREATE CONSTRAINT FOR (d:Disease) REQUIRE d.name IS UNIQUE版本差異會在 5.1 展開講。跑完約束后再用 Neo4j Browser 的可視化視圖抽查兩條帶關(guān)系的路徑箭頭方向能直接暴露導(dǎo)入腳本里的反向錯誤這一步比看日志直觀得多。3. 問句解析與實體識別把“糖尿病能不能吃西瓜”拆成能檢索的零件問答鏈路里的黑匣子不是 Cypher而是“從一句話里找到實體并判斷意圖”。用戶問“高血壓能不能吃阿司匹林”句子里有兩個實體系統(tǒng)得知道主實體是阿司匹林、意圖是禁忌查詢這步錯了后面圖再準(zhǔn)也白搭。3.1 詞典AC自動機(jī)先于NER模型的三條理由醫(yī)療問答場景里詞典匹配通常比訓(xùn)練 NER 模型更劃算。第一醫(yī)療實體幾乎都是專有名詞常見病、常用藥幾千條別名就能覆蓋業(yè)務(wù)大頭詞典覆蓋率足夠高第二標(biāo)注語料很貴醫(yī)療 NER 要處理超長實體和嵌套實體冷啟動成本不是小項目能接受的第三NER 模型輸出的是概率詞典輸出的是證據(jù)——命中哪個詞、對應(yīng)哪個標(biāo)準(zhǔn)名出了問題可以直接查。用 pyahocorasick 做多模式匹配一次遍歷就能命中所有詞典詞條import ahocorasick import csv class MedicalNER: def __init__(self, dict_csv): self.auto ahocorasick.Automaton() with open(dict_csv, encodingutf-8) as f: for row in csv.DictReader(f): alias row[alias].strip() self.auto.add_word( alias, (len(alias), row[entity_type], row[std_name]) ) self.auto.make_automaton() def extract(self, question): hits [] for end_idx, (length, etype, std_name) in self.auto.iter(question): start_idx end_idx - length 1 hits.append((start_idx, length, etype, std_name)) hits.sort(keylambda x: (x[1], -x[0]), reverseTrue) return hits邏輯說明add_word 把每個別名加入自動機(jī)value 里存詞長、實體類型、標(biāo)準(zhǔn)名extract 遍歷問句拿到所有命中按“長詞優(yōu)先”排序排序后由主流程做重疊過濾保證“高血壓”命中時不會同時保留“高血”這個殘缺匹配。參數(shù)說明dict_csv 至少要有 alias、entity_type、std_name 三列別名一行一個如果同一個別名出現(xiàn)兩次后寫的會覆蓋先寫的導(dǎo)入詞典前先做去重Aho-Corasick 對中文同樣適用不需要額外分詞但要注意別名里帶括號或空格時匹配前最好把問句和詞典做同樣的歸一化處理。3.2 意圖分類先抓否定詞再按關(guān)鍵詞模板分層意圖分類不需要上模型短文本里關(guān)鍵詞互斥性很強(qiáng)規(guī)則模板足夠穩(wěn)。先把意圖切成五類癥狀查病、疾病查藥、藥品禁忌、疾病查科室、藥品用法。分類順序很重要否定和禁忌意圖必須最先判斷。NEG_KEYWORDS [不能, 不可以, 禁忌, 忌, 禁用, 慎用] def classify(question, entities): # 否定/禁忌意圖優(yōu)先于治療意圖 for w in NEG_KEYWORDS: if w in question: return DRUG_CONTRADICTION if 掛什么科 in question or 掛科 in question or 科室 in question: return DISEASE_TO_DEPT if 癥狀 in question or 表現(xiàn) in question or 什么病 in question: return SYMPTOM_TO_DISEASE if any(t Medicine for _, _, t, _ in entities): if 怎么用 in question or 怎么吃 in question or 劑量 in question: return DRUG_USAGE return DISEASE_TO_MEDICINE邏輯說明順序即優(yōu)先級禁忌 科室 癥狀 藥品用法 默認(rèn)治療。先處理否定詞防止“高血壓不能吃什么藥”落到治療意圖句子里帶 Medicine 實體且出現(xiàn)用法詞時走 DRUG_USAGE否則默認(rèn)落到疾病治療查詢空結(jié)果由 4.2 的兜底邏輯處理。參數(shù)說明NEG_KEYWORDS 要按業(yè)務(wù)持續(xù)維護(hù)漏掉“忌口”比多寫一個“不能”代價更大這類詞寧可多寫分類結(jié)果建議打進(jìn)日志問答回放時能直接看出意圖判斷錯在哪一步。多實體場景下還要定主實體一句里出現(xiàn)“高血壓”和“阿司匹林”時意圖是 DRUG_CONTRADICTION主實體取藥品而非疾病因為禁忌關(guān)系的起點是藥品。常見做法是定義各意圖的主實體類型優(yōu)先級比如含藥品時優(yōu)先選 Medicine否則取 Disease 或 Symptom這個邏輯在 4.1 的 pick_primary_entity 里落地。4. 從意圖到 Cypher 再到答案模板映射與結(jié)果兜底意圖和實體都齊了下一步是把它們翻譯成圖查詢。這一步最容易寫出“能跑但不敢上線”的代碼——字符串拼 Cypher、不設(shè) LIMIT、空結(jié)果直接拋異常都是初學(xué)者常踩的坑。4.1 意圖模板表與參數(shù)化查詢別再拼字符串每個意圖對應(yīng)一個 Cypher 模板$entity 指的是標(biāo)準(zhǔn)化后的實體名也就是 3.1 里 std_name 字段的值。初版模板保持一跳關(guān)系就夠擴(kuò)展多跳留給后面的優(yōu)化階段。意圖問句示例Cypher 模板返回SYMPTOM_TO_DISEASE頭暈伴多飲是什么病MATCH (d:Disease)-[:HAS_SYMPTOM]-(s:Symptom) WHERE s.name $entity RETURN d.name, d.desc最多 10 種疾病及簡介DISEASE_TO_MEDICINE2型糖尿病吃什么藥MATCH (m:Medicine)-[:TREAT]-(d:Disease) WHERE d.name $entity RETURN m.name最多 10 種藥品DRUG_CONTRADICTION高血壓能吃阿司匹林嗎MATCH (m:Medicine {name:$entity})-[:CONTRAINDICATED]-(d:Disease) RETURN d.name禁忌疾病列表DISEASE_TO_DEPT心悸掛什么科MATCH (d:Disease {name:$entity})-[:VISIT_DEPT]-(dept:Department) RETURN dept.name科室名稱DRUG_USAGE阿卡波糖怎么吃MATCH (m:Medicine {name:$entity}) RETURN m.usage, m.dosage用法用量Python 端把模板存成字典執(zhí)行時統(tǒng)一走參數(shù)化查詢TEMPLATES { SYMPTOM_TO_DISEASE: ( MATCH (d:Disease)-[:HAS_SYMPTOM]-(s:Symptom) WHERE s.name $entity RETURN d.name AS name, d.desc AS desc LIMIT 10 ), DISEASE_TO_MEDICINE: ( MATCH (m:Medicine)-[:TREAT]-(d:Disease) WHERE d.name $entity RETURN m.name AS name LIMIT 10 ), DRUG_CONTRADICTION: ( MATCH (m:Medicine {name:$entity})-[:CONTRAINDICATED]-(d:Disease) RETURN d.name AS name LIMIT 10 ), } def query_answer(question, entities, intent): std_name pick_primary_entity(entities, intent) if not std_name: return [] cypher TEMPLATES[intent] with driver.session() as session: result session.run(cypher, entitystd_name) return list(result)邏輯說明所有實體值全部走 $ 占位符由驅(qū)動安全序列化藥品名里出現(xiàn)引號或百分號也不會破壞查詢結(jié)構(gòu)這是參數(shù)化查詢對字符串拼接的降維打擊query_answer 按意圖選模板取主實體后執(zhí)行主實體為空時直接返回不發(fā) Cypher。參數(shù)說明LIMIT 統(tǒng)一控制在 10防止把幾千條關(guān)系一次拉回RETURN 里的 AS 別名方便后續(xù)按字段名取值pick_primary_entity 的規(guī)則在 3.2 講過藥品優(yōu)先其次疾病最后癥狀意圖不同優(yōu)先級也不同。4.2 三態(tài)結(jié)果與答案包裝有、無、太多查詢結(jié)果只有三種情況分別處理才是完整問答。有結(jié)果時做格式化輸出無結(jié)果時先區(qū)分“圖譜缺數(shù)據(jù)”還是“實體識別錯”結(jié)果太多時截斷展示并給用戶追問空間。MAX_DISPLAY 5 def render(records, intent): if not records: return 記錄里暫時查不到這個答案換個說法或檢查名稱后重試。 names [r[name] for r in records[:MAX_DISPLAY]] text 、.join(names) if len(records) MAX_DISPLAY: text f 等{len(records)}種 return text 。以上結(jié)果僅供查詢參考具體以臨床醫(yī)生意見為準(zhǔn)。邏輯說明展示層面截斷到 5 條多余的用總數(shù)說明避免答案列表失控醫(yī)療問答必須帶免責(zé)后綴這是底線不能省。參數(shù)說明MAX_DISPLAY 設(shè) 5 是因為中文列表一行放 3 到 4 項5 條差不多一屏如果一條查詢經(jīng)常返回 10 條以上說明圖的連接太密應(yīng)該從模板側(cè)收斂而不是在展示側(cè)硬撐。無結(jié)果的排查要可操作拿 std_name 再發(fā)一條MATCH (x) WHERE x.name $entity RETURN labels(x)如果節(jié)點不存在說明詞典和圖的實體沒有對齊如果節(jié)點存在但關(guān)系為空說明模板寫的路徑或關(guān)系方向有問題。這個判斷邏輯可以直接寫進(jìn)問答日志作為診斷字段排錯時少走彎路。5. 醫(yī)療問答項目的五個常見問題與排查記錄版本、編碼與實體命中這一章是給同類項目排障后整理的踩坑記錄每一條都按“現(xiàn)象 → 原因 → 解決”寫你能直接對照自己的報錯信息定位。5.1 Neo4j 版本不一致3.5 的寫法在 5.x 里報錯現(xiàn)象不少這類項目把 Neo4j 鎖在 3.5 版本而你機(jī)器上新裝的是桌面版或社區(qū)版 5.x。按照舊說明執(zhí)行 LOAD CSV 或創(chuàng)建約束時直接報語法錯誤提示 unknown function 或 procedure not found。原因Neo4j 跨大版本語法不兼容。3.x 時代的CREATE CONSTRAINT ON (d:Disease) ASSERT d.name IS UNIQUE在 4.x 后改成了CREATE CONSTRAINT FOR (d:Disease) REQUIRE d.name IS UNIQUE另外 3.5 時代有些腳本依賴 APOC 擴(kuò)展存儲過程新版不會隨安裝包附帶需要單獨裝。解決鎖版本是最省事的路徑。Neo4j 官方 Archives 頁面能選歷史版本Docker 鏡像也保留著對應(yīng) tag按項目說明的版本部署就不會有語法遷移問題。驗證型項目不值得把時間耗在升級語法上這是踩過之后才想明白的后悔藥。如果非要跑新版把查詢里的約束語法、存儲過程調(diào)用統(tǒng)一按新版改寫并確認(rèn) APOC 是否已安裝。5.2 CSV 中文變亂碼Excel 保存的 GBK 與 Neo4j 的 UTF-8現(xiàn)象LOAD CSV 完成后中文名稱全是方塊或亂碼按中文名查詢關(guān)系全部落空。原因Excel 另存的 CSV 默認(rèn) ANSI/GBK 編碼Neo4j 按 UTF-8 讀取字節(jié)錯位導(dǎo)致亂碼。解決導(dǎo)入前統(tǒng)一轉(zhuǎn)成 UTF-8用 Python 幾行代碼搞定。with open(disease_gbk.csv, encodinggbk) as f: text f.read() with open(disease_utf8.csv, w, encodingutf-8, newline) as f: f.write(text)邏輯說明先用 gbk 把 Excel 導(dǎo)出的文件讀回內(nèi)存再按 utf-8 寫新文件順手把表頭也一起規(guī)范一下。不要在 Neo4j 端硬解編碼導(dǎo)入層統(tǒng)一最省心。參數(shù)說明newline 防止 Windows 下寫入時空行被插入如果源文件本身就是 UTF-8用 errorsreplace 快速定位壞字節(jié)比逐行猜編碼更高效。5.3 實體識別命中“高血”而不是“高血壓”現(xiàn)象問“高血壓注意什么”實體列表里出現(xiàn)“高血”或“血壓”這種子串按錯誤實體進(jìn)圖查詢返回空結(jié)果。原因詞典里多個別名存在前綴重疊匹配邏輯只做了單詞命中沒有做最長匹配優(yōu)先。解決在 3.1 的 extract 返回后加一個重疊過濾排序后從左往右掃重疊區(qū)間只保留最長的詞條。best, last_end [], 0 for start, length, etype, std_name in hits: if start last_end: continue best.append((std_name, etype)) last_end start length邏輯說明按長詞優(yōu)先排序后取第一個未重疊的命中后面與區(qū)間重疊的字段全部跳過保證“高血壓”覆蓋“高血”。參數(shù)說明last_end 隨采納詞條的結(jié)束位置更新漏掉這一步會出現(xiàn)間隔性跳過錯誤比如兩個相鄰實體只保留第一個。5.4 一次問答返回上萬條路徑圖查詢沒有 LIMIT現(xiàn)象單個問題響應(yīng)時間超過兩秒Neo4j Browser 可視化卡死日志里頻繁出現(xiàn)查詢超時告警。原因模板里漏寫 LIMIT數(shù)據(jù)庫把相關(guān)路徑全部展開多跳查詢更明顯路徑數(shù)呈指數(shù)級增長。解決所有模板統(tǒng)一加 LIMIT這是 4.1 已經(jīng)做好的事Neo4j 配置里打開事務(wù)超時常見做法是把 dbms.transaction.timeout 設(shè)為 5sPython 驅(qū)動連接池上限調(diào)小GraphDatabase.driver 加 max_connection_pool_size8。多跳查詢確實需要時先用 PROFILE 看 Expand 數(shù)量是否異常再決定要不要加索引。5.5 重復(fù)導(dǎo)入讓節(jié)點翻倍MERGE 和唯一約束是兩件套現(xiàn)象定時導(dǎo)入腳本每次運行后疾病節(jié)點數(shù)量翻倍同名節(jié)點散落多處關(guān)系也成對重復(fù)。原因?qū)肓?CREATE 而不是 MERGE或者用了 MERGE 但沒建唯一約束并發(fā)環(huán)境下仍可能產(chǎn)生重復(fù)。解決導(dǎo)入語句統(tǒng)一用 MERGE對 name 建唯一約束語法按 5.1 的版本差異來選管道跑完做一次冪等校驗。MATCH (d:Disease) WITH d.name AS n, count(*) AS c WHERE c 1 RETURN n, c;邏輯說明重復(fù)計數(shù)是檢驗導(dǎo)入腳本是否健壯的第一道門返回空說明節(jié)點唯一性沒問題注意唯一約束建立后更新實體用 SET 改屬性不要刪了重建否則掛在舊節(jié)點上的關(guān)系會全部斷開。6. 用驗證集把問答從“能跑”推到“可信”回放日志與三個擴(kuò)展判斷一套問答系統(tǒng)能不能用標(biāo)準(zhǔn)不是幾個測試問句答得好而是能穩(wěn)定知道“什么時候答不出”。我一般先準(zhǔn)備 50 到 100 條問答對覆蓋五個意圖和邊界場景比如否定問法、雙實體問法、空結(jié)果問法。標(biāo)好 expect 列后用評估腳本批量跑正確率、兜底率、錯答分布一次算清。import csv import logging def evaluate(qa_csv, answer_fn): ok 0 for row in csv.DictReader(open(qa_csv, encodingutf-8)): expect row[expect] got answer_fn(row[question]) if expect in got: ok 1 logging.info(PASS %s, row[question]) else: logging.warning(FAIL q%s expect%s got%s, row[question], expect, got) return ok邏輯說明用隸屬判斷而不是全等判斷因為回答是列表拼接文本標(biāo)準(zhǔn)答案只寫關(guān)鍵實體或短語即可logging 同時記錄失敗樣本方便反查是哪一環(huán)出了問題?;胤湃罩臼堑暮诵墓ぞ摺C恳粭l問答把 question、intent、std_name、返回條數(shù)、耗時寫一行每周統(tǒng)計兜底率。兜底率超過三成優(yōu)先回看是詞典缺詞還是模板缺類型2.1 里提到的同義詞問題會在回放日志里直接暴露成一批“查不到”的 query按日志補(bǔ)詞條比憑空想快得多。三個擴(kuò)展方向按性價比排序第一個是把實體識別接上大模型做增強(qiáng)詞典保底處理高頻大模型處理長尾口語但知識結(jié)果仍以圖數(shù)據(jù)庫為準(zhǔn)不要讓大模型直接生成三元組入庫醫(yī)療場景經(jīng)不起幻覺第二個是做多輪追問拿到 TOP5 疾病后反問癥狀用追加實體收窄結(jié)果這是單輪問答走向多輪對話的關(guān)鍵路徑第三個是增量更新管道從權(quán)威藥品說明書和臨床指南里結(jié)構(gòu)化抽取新增藥品與禁忌定期導(dǎo)入配合 5.5 的冪等校驗保證數(shù)據(jù)不翻倍。這個項目我做得不算順利最初把大量時間壓在 NER 模型選型上最后發(fā)現(xiàn)把詞典、模板和回放日志當(dāng)成一個閉環(huán)來迭代效果提升反而最快。工具會過時圖譜加日志的迭代習(xí)慣不會過時。希望幫到你。本文還有配套的精品資源點擊獲取