Dify 实战笔记:工作流核心玩法 + 开源 AI 应用全解析

背景

最近在做项目里面涉及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(为对话提供高层次指导)

bash 复制代码
你是一个医疗预检分诊 AI 助手,负责根据患者主诉进行关键追问。

## 追问原则
1. 围绕症状的「发病时间、部位、程度、伴随症状、诱因」等关键维度
2. 参考患者健康档案的基础病,做针对性追问(如高血压患者问血压)
3. 如医生提供了打回理由,必须按医生指示方向提问
4. 语气友好关切,避免给出诊断结论
5. 一次最多追问 2 个问题,不可过多
6. 直接输出追问内容(纯文本),禁止 JSON、禁止 markdown 代码块

# 知识库参考
请优先参考知识库检索结果中的分诊规则、急危重症提示和问诊规范。
如果知识库内容与当前患者描述无关,不要强行引用。

## 示例输出
请问您的头痛是搏动性的还是持续性钝痛?是否伴有恶心呕吐或畏光症状?

然后是User(向模型提供指令)

bash 复制代码
# 患者健康档案
{{#start_1.patient_profile#}}

# 医生打回理由(如有)
{{#start_1.doctor_reason#}}

# 对话历史
{{#start_1.history#}}

# 患者最新消息
{{#start_1.user_message#}}

#知识库参考
{{#context#}}

请结合知识库参考、患者健康档案、医生打回理由和对话历史,输出 1-2 个关键追问(纯文本)。

最终输出是一个变量:

下面是综合分诊,需要用户主动提交

首先是SYSTEM(为对话提供高层次指导)

bash 复制代码
你是一个医疗预检分诊专家 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(向模型提供指令)

bash 复制代码
# 患者健康档案
{{#start_1.patient_profile#}}

# 医生打回理由(如有)
{{#start_1.doctor_reason#}}

# 完整对话历史
{{#start_1.history#}}

# 患者最新消息
{{#start_1.user_message#}}

# 知识库参考
{{#context#}}

请结合知识库参考、患者健康档案、医生打回理由和完整对话历史,输出分诊 JSON。

开放接口就选择"访问API",进行配置,即可

查看下HTTP调用的例子:

看下这个开源项目里面python是如何调用的:

python 复制代码
class DifyClient:
    """Dify 工作流调用客户端。

    本项目使用模块底部的全局单例 `dify_client` 即可,一般无需手动实例化。

    使用方式:
        result = await dify_client.run_workflow(
            user_message="头痛、发烧两天",
            conversation_id=1,
            patient_profile="32岁 女 高血压3年 青霉素过敏",
            target_node="followup",
            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": f"Bearer {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_ascii=False),
                "doctor_reason": doctor_reason or "",
            },
            # blocking:一次性返回完整 JSON;后端要解析 is_final 才能决定状态机跳转,
            # 因此不能用 streaming(SSE 流式更适合给前端做打字机效果)。
            "response_mode": "blocking",
            # Dify 用 user 标识做对话隔离 + 限流计数,同会话保持一致即可
            "user": f"patient-{conversation_id}",
        }

        # ---------- 发起 HTTP 请求 ----------
        # 用 async with 上下文确保连接关闭,避免连接泄漏;每次创建短连接以隔离故障。
        try:
            async with httpx.AsyncClient(timeout=self.timeout) as client:
                resp = await client.post(url, headers=self._headers, json=payload)
                print("Dify原始返回报文:", resp.text)
                print("请求发送的body:", json.dumps(payload, ensure_ascii=False))
                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(f"Dify 工作流调用失败:{exc}") from exc

        # ---------- 解析响应并返回统一契约 ----------
        return self._parse_response(data, target_node=target_node)

可知都放到input里面。

优化提升点

知识库的准确性,是最重要,得准备好数据,进行召回测试

如果使用其他方式,可能会让整改工作流变慢,这种呢,准确性很感觉。感觉这个地方是提升的重点。

相关推荐
dong1326972 小时前
Agent Skills学习笔记
笔记·学习
liferecords3 小时前
阅读笔记:ExtractBench: A Benchmark for Schema-Guided Enterprise Document Extraction
笔记
尤乐娃子5 小时前
进入大厂(厂子大)实习Day8
前端·笔记·实习
一只小菜鸡..5 小时前
南京大学 操作系统 (JYY) 学习笔记:持久化的黑魔法——从磁铁、刻坑到闪存电荷
服务器·笔记·学习
aFakeProgramer5 小时前
Linux版本兼容问题及RTA-VRTE环境配置笔记
linux·笔记
龙仔7256 小时前
RustDesk 完整笔记
运维·笔记·rust·远程工具·rustdesk
HJX_07246 小时前
嵌入式软件C语言八股文复习笔记5——编译、链接与底层硬核机制
c语言·开发语言·笔记
摇滚侠7 小时前
《SpringBoot 3:入门与应用实战》第 15 章 生产级特性 监控指标 Metrics 阅读笔记
java·spring boot·笔记
噜~噜~噜~8 小时前
操作系统笔记-1.1.3 操作系统的特征
笔记·操作系统