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