戰(zhàn)筆記:工作流核心玩法 + 開源 AI 應(yīng)用全解析)
背景最近在做項(xiàng)目里面涉及AI應(yīng)用領(lǐng)導(dǎo)選型為dify為此需要研究下關(guān)于AI應(yīng)用大家是如何做的在此分析了medical-consultation-system項(xiàng)目https://gitee.com/hn2004214/medical-consultation-system受益匪淺此dify中的工作流具有通用性。分析前端后端數(shù)據(jù)庫在此就不在分析僅僅分析dify中的AI應(yīng)用以及如何用最簡單方法調(diào)用AI應(yīng)用。從dify中的圖標(biāo)可以看出這里創(chuàng)建的就是“工作流”。進(jìn)入后流程還是比較簡單的開始時開始有幾個參數(shù)如下從中可以知道這個就是調(diào)用AI應(yīng)用的輸入因?yàn)槭轻t(yī)療系統(tǒng)嘛大家應(yīng)該都明白的在此就說明一個東西history對話歷史JSON這個是最關(guān)鍵的API調(diào)用不像使用對話框大模型是沒有記憶功能的需要用戶在每一次交流時帶上歷史的對話進(jìn)行AI再進(jìn)行分析。第二個節(jié)點(diǎn)就是知識庫查詢第三個節(jié)點(diǎn)就是路由判斷判斷是繼續(xù)追問還是得出分診結(jié)果??聪伦穯柹傻腖LM邏輯這個qwen3:0.6b是我自己配置調(diào)試能用真實(shí)項(xiàng)目看自己配置首先是SYSTEM為對話提供高層次指導(dǎo)你是一個醫(yī)療預(yù)檢分診 AI 助手負(fù)責(zé)根據(jù)患者主訴進(jìn)行關(guān)鍵追問。 ## 追問原則 1. 圍繞癥狀的「發(fā)病時間、部位、程度、伴隨癥狀、誘因」等關(guān)鍵維度 2. 參考患者健康檔案的基礎(chǔ)病做針對性追問如高血壓患者問血壓 3. 如醫(yī)生提供了打回理由必須按醫(yī)生指示方向提問 4. 語氣友好關(guān)切避免給出診斷結(jié)論 5. 一次最多追問 2 個問題不可過多 6. 直接輸出追問內(nèi)容純文本禁止 JSON、禁止 markdown 代碼塊 # 知識庫參考 請優(yōu)先參考知識庫檢索結(jié)果中的分診規(guī)則、急危重癥提示和問診規(guī)范。 如果知識庫內(nèi)容與當(dāng)前患者描述無關(guān)不要強(qiáng)行引用。 ## 示例輸出 請問您的頭痛是搏動性的還是持續(xù)性鈍痛是否伴有惡心嘔吐或畏光癥狀然后是User向模型提供指令# 患者健康檔案 {{#start_1.patient_profile#}} # 醫(yī)生打回理由如有 {{#start_1.doctor_reason#}} # 對話歷史 {{#start_1.history#}} # 患者最新消息 {{#start_1.user_message#}} #知識庫參考 {{#context#}} 請結(jié)合知識庫參考、患者健康檔案、醫(yī)生打回理由和對話歷史輸出 1-2 個關(guān)鍵追問純文本。最終輸出是一個變量下面是綜合分診需要用戶主動提交首先是SYSTEM為對話提供高層次指導(dǎo)你是一個醫(yī)療預(yù)檢分診專家 AI負(fù)責(zé)根據(jù)患者對話、健康檔案給出結(jié)構(gòu)化分診建議。 ## 輸出格式嚴(yán)格 JSON禁止任何 markdown 代碼塊或額外文字 { is_final: true, recommended_dept: 神經(jīng)內(nèi)科, possible_causes: [偏頭痛, 緊張型頭痛], advice: 建議充分休息并監(jiān)測體溫若癥狀持續(xù)或加重請前往急診。, urgency_level: 一般, answer: 根據(jù)您的描述結(jié)合您的健康檔案初步建議前往神經(jīng)內(nèi)科…… } ## 字段約束 1. is_final**必須嚴(yán)格為 true**標(biāo)識分診建議已最終生成供后端更新狀態(tài) 2. recommended_dept常見一級/二級科室如 內(nèi)科 / 外科 / 神經(jīng)內(nèi)科 / 呼吸內(nèi)科 / 消化內(nèi)科 / 心血管內(nèi)科 / 骨科 / 皮膚科 / 兒科 / 急診科 / 婦科 / 泌尿外科 / 耳鼻喉科 等 3. possible_causes數(shù)組列出 2-3 個最可能的病因 4. advice居家護(hù)理或就醫(yī)建議不超過 100 字 5. urgency_level**必須嚴(yán)格是** 一般 / 緊急 / 危重 三者之一 - 危重意識障礙、胸痛胸悶、呼吸困難、大出血、持續(xù)劇烈頭痛 - 緊急持續(xù)高熱、劇烈疼痛、癥狀持續(xù)加重 - 一般常見輕度癥狀 6. answer給患者看的人性化解釋語氣溫和專業(yè)不超過 200 字 # 知識庫參考 請優(yōu)先參考知識庫檢索結(jié)果中的分診規(guī)則、急危重癥提示和問診規(guī)范。 如果知識庫內(nèi)容與當(dāng)前患者描述無關(guān)不要強(qiáng)行引用。 ## 注意事項(xiàng) - 如果醫(yī)生提供了打回理由必須在建議中明確體現(xiàn)對該理由的回應(yīng) - 如果打回理由中含有【醫(yī)生僅要求修改以下字段】提示請**重點(diǎn)重新生成這些字段**其他字段盡量與上次分診保持一致同時 answer 要圍繞這些字段的修改進(jìn)行解釋 - 結(jié)合患者健康檔案中的既往病史、過敏史、家族史等因素綜合判斷 - 絕對不要輸出 JSON 之外的任何內(nèi)容然后是User向模型提供指令# 患者健康檔案 {{#start_1.patient_profile#}} # 醫(yī)生打回理由如有 {{#start_1.doctor_reason#}} # 完整對話歷史 {{#start_1.history#}} # 患者最新消息 {{#start_1.user_message#}} # 知識庫參考 {{#context#}} 請結(jié)合知識庫參考、患者健康檔案、醫(yī)生打回理由和完整對話歷史輸出分診 JSON。開放接口就選擇“訪問API”進(jìn)行配置即可查看下HTTP調(diào)用的例子看下這個開源項(xiàng)目里面python是如何調(diào)用的class DifyClient: Dify 工作流調(diào)用客戶端。 本項(xiàng)目使用模塊底部的全局單例 dify_client 即可一般無需手動實(shí)例化。 使用方式 result await dify_client.run_workflow( user_message頭痛、發(fā)燒兩天, conversation_id1, patient_profile32歲 女 高血壓3年 青霉素過敏, target_nodefollowup, history[{role: user, content: ...}, ...], ) def __init__( self, api_base: str | None None, api_key: str | None None, timeout: float 60.0, ) - None: # api_base / api_key 默認(rèn)從應(yīng)用配置讀取可被參數(shù)覆蓋以便單測注入 mock # rstrip(/) 防止配置里末尾多寫斜杠導(dǎo)致 URL 拼出 //workflows/run self.api_base (api_base or settings.dify_api_base).rstrip(/) self.api_key api_key or settings.dify_api_key # LLM 推理可能耗時較長給到 60 秒超時 self.timeout timeout property def _headers(self) - dict[str, str]: Dify 要求所有請求攜帶 Bearer token每個工作流綁定一個獨(dú)立 API Key。 return { Authorization: fBearer {self.api_key}, Content-Type: application/json, } async def run_workflow( self, *, user_message: str, conversation_id: int, patient_profile: str, target_node: str, history: list[dict[str, str]] | None None, doctor_reason: str | None None, ) - dict[str, Any]: 調(diào)用 Dify 工作流 /workflows/run 接口本客戶端的唯一對外入口。 Args: user_message: 患者最新一條消息文本用于 LLM 推理與知識檢索 query。 conversation_id: 會話 ID僅用于 Dify 端日志追蹤與限流隔離。 patient_profile: 患者檔案摘要已由 profile_service 拼接為單行字符串。 target_node: 工作流路由開關(guān)僅接受 triage 或 followup。 對應(yīng) YAML 中 if_else_1 節(jié)點(diǎn)的判斷條件。 history: 對話歷史 [{role, content}, ...]會被 json.dumps 后 作為字符串變量傳入工作流Dify 變量類型限制。 doctor_reason: 醫(yī)生打回時的修正意見非打回場景傳 None。 Returns: 統(tǒng)一契約 dict { is_final: bool, # True 時上層將更新會話狀態(tài)為 pending_review assistant_text: str, # AI 回復(fù)文本給患者看 triage_payload: dict | None, # 綜合分診節(jié)點(diǎn)的結(jié)構(gòu)化結(jié)果4 個字段 } Raises: RuntimeError: 網(wǎng)絡(luò)異?;?Dify 返回非 2xx 時拋出由上層 service 捕獲處理。 # ---------- 降級分支未配置 Dify 時直接返回 Mock 數(shù)據(jù) ---------- # 占位符 your-dify* 是 .env.example 里的默認(rèn)值 # 用 startswith(your-dify) 兼容用戶忘改 .env 的情況。 if not self.api_key or self.api_key.startswith(your-dify): logger.warning(DIFY_API_KEY 未配置或?yàn)檎嘉环褂?Mock 模擬返回) return self._mock_response(user_message, target_node) # ---------- 構(gòu)造請求 payload ---------- # inputs 中 6 個 key 必須與 Dify 工作流 開始 節(jié)點(diǎn)定義的 6 個變量一一對應(yīng)。 # 任何 key 缺失或拼寫不一致都會被 Dify 返回 400。 url f{self.api_base}/workflows/run payload { inputs: { # 與 YAML 中 start_1.variables 嚴(yán)格對齊 user_message: user_message, conversation_id: str(conversation_id), # Dify text-input 類型要求字符串 patient_profile: patient_profile or , target_node: target_node, # 決定走 triage 還是 followup 分支 # Dify 不支持復(fù)雜結(jié)構(gòu)變量對話歷史需序列化為 JSON 字符串后再注入 prompt history: json.dumps(history or [], ensure_asciiFalse), doctor_reason: doctor_reason or , }, # blocking一次性返回完整 JSON后端要解析 is_final 才能決定狀態(tài)機(jī)跳轉(zhuǎn) # 因此不能用 streamingSSE 流式更適合給前端做打字機(jī)效果。 response_mode: blocking, # Dify 用 user 標(biāo)識做對話隔離 限流計數(shù)同會話保持一致即可 user: fpatient-{conversation_id}, } # ---------- 發(fā)起 HTTP 請求 ---------- # 用 async with 上下文確保連接關(guān)閉避免連接泄漏每次創(chuàng)建短連接以隔離故障。 try: async with httpx.AsyncClient(timeoutself.timeout) as client: resp await client.post(url, headersself._headers, jsonpayload) print(Dify原始返回報文, resp.text) print(請求發(fā)送的body, json.dumps(payload, ensure_asciiFalse)) resp.raise_for_status() # 4xx/5xx 拋 HTTPStatusError data resp.json() except httpx.HTTPError as exc: # 把 httpx 體系下所有異常統(tǒng)一為業(yè)務(wù)側(cè)的 RuntimeError # 上層 service 不必感知 httpx 細(xì)節(jié)反腐層關(guān)鍵設(shè)計。 logger.exception(調(diào)用 Dify 工作流失敗%s, exc) raise RuntimeError(fDify 工作流調(diào)用失敗{exc}) from exc # ---------- 解析響應(yīng)并返回統(tǒng)一契約 ---------- return self._parse_response(data, target_nodetarget_node)可知都放到input里面。優(yōu)化提升點(diǎn)知識庫的準(zhǔn)確性是最重要得準(zhǔn)備好數(shù)據(jù)進(jìn)行召回測試如果使用其他方式可能會讓整改工作流變慢這種呢準(zhǔn)確性很感覺。感覺這個地方是提升的重點(diǎn)。