封装 Dify 智能助手 Python API:高效集成 AI 助手能力 - 对话API封装

封装 Dify 智能助手 Python API:高效集成 AI 助手能力 - 对话API封装

摘要

本文将详细介绍如何将 Dify 智能聊天助手对话API封装为 Python API,通过创建 DifyClient 类实现对Dify API的便捷调用,支持阻塞和流式两种响应模式。深入探讨了异常处理机制、流式响应解析以及文件处理等关键功能的实现细节,并提供完整代码示例,帮助开发者快速将 Dify AI 助手能力集成到 Python 应用中,提升应用的智能交互体验。

当下,将智能助手能力集成到应用程序中的需求愈发强烈和重要。Dify 作为一个强大的 AI 平台,提供了丰富的 API 接口,但直接调用这些接口往往需要处理复杂的请求细节和响应格式。为了简化这一过程,可以通过封装一个 Python API 客户端,将 Dify 的能力无缝集成到 Python 应用中。

为什么封装 Dify API?

直接使用 Dify API 虽然可行,但存在以下问题:

  1. 需要重复处理认证、请求头等基础细节
  2. 响应格式复杂,尤其是流式响应需要特殊处理
  3. 错误处理分散,难以统一管理
  4. 代码可读性和可维护性较差

通过封装一个专门的 Python 客户端,可以实现:

  • 统一处理认证和请求配置
  • 提供简洁的接口抽象复杂实现细节
  • 集中管理错误处理逻辑
  • 支持阻塞和流式两种响应模式
  • 提供类型提示增强代码可读性

核心类设计

这里设计一个 DifyClient 类作为与 Dify API 交互的核心组件。这个类包含以下关键部分:

class DifyClient:
    def __init__(self, api_key: str, api_url: str = "http://localhost/v1"):
        """
        Initialize Dify API client
        
        :param api_key: API authentication key
        :param base_url: Base API URL (default: http://localhost/v1)
        """
        self.api_key = api_key
        self.base_url = api_url.rstrip('/')
        self.session = requests.Session()
        self.session.headers.update({
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json"
        })

构造函数负责初始化客户端,设置 API 密钥和基础 URL,并配置请求会话的公共头部信息,包括认证令牌和内容类型。

消息发送与响应处理

DifyClient 类的核心功能是发送消息并处理响应。可以提供一个 send_message 方法统一处理不同模式的请求:

def send_message(
    self,
    query: str,
    response_mode: Literal["blocking", "streaming"],
    inputs: Optional[Dict[str, Any]] = None,
    conversation_id: Optional[str] = None,
    user: Optional[str] = None,
    files: Optional[List[Dict[str, str]]] = None,
    parse_stream: bool = True
) -> Union[Dict[str, Any], Generator[Dict[str, Any], None, None]]:
    """
    发送对话消息
    
    :param query: 用户查询
    :param response_mode: 回复模式 (阻塞或流式)
    :param inputs: 附加输入参数
    :param conversation_id: 对话ID,可选
    :param user: 用户标识
    :param files: 文件列表,可选
    :param parse_stream: 是否解析流式响应,默认True,若为False则直接返回流式响应数据
    
    :return: 阻塞模式返回响应数据,流式模式返回生成器
    """
    url = f"{self.base_url}/chat-messages"
    payload = {
        "inputs": inputs or {},
        "query": query,
        "response_mode": response_mode,
        "conversation_id": conversation_id or "",
        "user": user or "default-user",
        "files": files or []
    }
    
    try:
        if response_mode == "blocking":
            return self._handle_blocking_request(url, payload)
        if parse_stream:
            return self._handle_streaming_request(url, payload)
        else:
            return self._stream_raw_response(url, payload)
    except requests.exceptions.RequestException as e:
        raise DifyAPIError(f"Request failed: {str(e)}") from e

这个方法根据 response_mode 参数决定使用阻塞模式还是流式模式处理请求。阻塞模式直接返回完整响应,而流式模式返回一个生成器,逐步产出响应内容。

阻塞模式处理

对于阻塞模式请求,使用 _handle_blocking_request 方法进行处理:

def _handle_blocking_request(self, url: str, payload: Dict[str, Any]) -> Dict[str, Any]:
    """处理阻塞模式请求"""
    response = self.session.post(url, json=payload)
    if not response.ok:
        raise DifyAPIError(f"API request failed: {response.text}", response.status_code)
    
    # 检查响应内容是否为空
    if not response.text.strip():
        raise DifyAPIError("Empty response received", response.status_code)
    
    try:
        return response.json()
    except json.JSONDecodeError:
        raise DifyAPIError(
            f"Failed to parse JSON response: {response.text[:100]}...",
            response.status_code
        )

这个方法发送 POST 请求,检查响应状态,处理空响应和 JSON 解析错误,并最终返回解析后的 JSON 数据。

流式模式处理

流式模式的处理更为复杂,可使用 _handle_streaming_request 方法:

def _handle_streaming_request(self, url: str, payload: Dict[str, Any]) -> Generator[Dict[str, Any], None, None]:
    """改进版流式请求处理"""
    with self.session.post(url, json=payload, stream=True, timeout=30) as response:
        if not response.ok:
            raise DifyAPIError(f"API request failed: {response.text}", response.status_code)
        
        result = StreamResult()
        for line in response.iter_lines(decode_unicode=True):
            event = self._parse_stream_line(line)
            event_type = event.get("event")

            # 动态检测助手类型
            if not result.is_agent and event_type in ["agent_thought", "agent_message"]:
                result.is_agent = True

            yield from self._process_event(event, result)

这个方法使用生成器逐步处理流式响应的每一行数据,并根据事件类型调用 _process_event 方法进一步处理。

流式响应解析

流式响应的解析是整个封装的关键部分。下面设计 StreamResult 类作为统一的结果容器:

class StreamResult:
    """统一流式处理结果容器"""
    def __init__(self):
        self.content = ""       # 聚合的回复内容
        self.metadata = None    # 最终元数据
        self.files = []         # 文件资源列表
        self.thoughts = []      # 智能助手思考链
        self.current_tool = {}  # 当前使用的工具信息
        self.is_agent = False   # 是否为智能助手模式

DifyClient类还实现了 _parse_stream_line 方法解析流式响应的每一行数据:

@staticmethod
def _parse_stream_line(line: str) -> Dict[str, Any]:
    """解析流式响应的单行数据"""
    try:
        # 假设流式响应以"data:"开头,后面跟着JSON数据
        if line.startswith("data:"):
            json_str = line[5:].strip()  # 移除"data:"前缀
            if json_str:  # 确保有实际内容
                return json.loads(json_str)
        return {"raw": line}
    except json.JSONDecodeError:
        return {"error": "Invalid JSON data", "raw": line}

以及 _process_event 方法处理不同类型的事件:

def _process_event(self, event: Dict[str, Any], result: StreamResult) -> Generator[Dict[str, Any], None, None]:
    """处理单个事件并更新结果"""
    event_type = event.get("event")
    
    # 公共事件处理
    if event_type == "message_end":
        result.metadata = event.get("metadata")
        yield {"type": "metadata", "data": result.metadata}
        return
    
    # 基础助手处理
    if not result.is_agent:
        if event_type == "message":
            result.content += event.get("answer", "")
            yield {"type": "message_chunk", "data": event.get("answer")}
        return
    
    # 智能助手处理
    if event_type == "agent_thought":
        thought_data = {
            "tool": event.get("tool"),
            "input": event.get("tool_input"),
            "observation": event.get("observation")
        }
        result.thoughts.append(thought_data)
        yield {"type": "thought", "data": thought_data}
    
    elif event_type == "agent_message":
        result.content += event.get("answer", "")
        yield {"type": "message_chunk", "data": event.get("answer")}
    
    elif event_type == "message_file":
        file_data = {
            "id": event.get("id"),
            "type": event.get("type"),
            "url": event.get("url")
        }
        result.files.append(file_data)
        yield {"type": "file", "data": file_data}

异常处理

为了更好地处理 API 调用过程中可能出现的错误,定义一个 DifyAPIError 异常类:

class DifyAPIError(Exception):
    """Base exception for Dify API errors"""
    def __init__(self, message: str, status_code: Optional[int] = None, response_text: Optional[str] = None):
        super().__init__(message)
        self.status_code = status_code
        self.response_text = response_text

这个异常类可以携带 HTTP 状态码和原始响应文本,方便进行详细的错误诊断。

使用示例

封装完成后,使用这个 Python API 客户端变得非常简单:

if __name__ == "__main__":
    # 初始化客户端
    client = DifyClient(api_key="app-************************")
    
    try:
        # 阻塞模式示例
        blocking_response = client.send_message(
            user='abc-123',
            query="What are the specs of the iPhone 13 Pro Max?",
            response_mode="blocking"
        )
        print("Blocking Response:", blocking_response)
        
        # 流式模式处理示例(解析流式数据)
        stream_gen = client.send_message(
            user='abc-123',
            query="生成日系动漫女孩图片",
            response_mode="streaming"
        )
        
        full_response = ""
        for chunk in stream_gen:
            chunk_type = chunk["type"]
            
            if chunk_type == "message_chunk":
                full_response += chunk["data"]
                print(f"内容更新: {full_response}")
            
            elif chunk_type == "thought":
                print(f"思考过程: {chunk['data']}")
            
            elif chunk_type == "file":
                print(f"生成文件: {chunk['data']['url']}")
            
            elif chunk_type == "metadata":
                print(f"最终元数据: {chunk['data']}")

        # 流式模式处理示例(直接返回原始数据流)
        for raw_line in client.send_message(
            user='abc-123',
            query="请你介绍一下唐代诗人李白",
            response_mode='streaming',
            parse_stream=False
        ):
            if raw_line.startswith("data:"):
                json_str = raw_line[5:].strip()  # 移除"data:"前缀
                if json_str:  # 确保有实际内容
                    print(json.loads(json_str))
            
        
    except DifyAPIError as e:
        print(f"请求失败: {str(e)}")

总结

通过封装 Dify 智能助手 API 为 Python API 客户端,可以大大简化与 Dify 平台的交互过程。这个封装不仅提供了简洁的接口,还处理了认证、错误管理、流式响应解析等复杂细节,让开发者可以更专注于核心业务逻辑。

这种封装方式具有以下优点:

  1. 统一的认证和请求配置
  2. 简洁的接口抽象复杂实现细节
  3. 集中的错误处理机制
  4. 支持多种响应模式灵活使用
  5. 良好的类型提示增强代码可读性

可以将这个封装集成到各种 Python 应用中,无论是 Web 应用、桌面应用还是移动应用的后端服务,都能快速获得智能助手能力,提升用户体验。

此外,这种封装模式也可以作为参考,用于其他 API 的 Python 客户端开发。

☞ 完整代码(附):

# !/usr/bin/env python3
# coding=utf-8
# Author: Lczdyx@CSDN



import json
import requests
from typing import Any, Dict, Generator, List, Literal, Optional, Union

class DifyAPIError(Exception):
    """Base exception for Dify API errors"""
    def __init__(self, message: str, status_code: Optional[int] = None, response_text: Optional[str] = None):
        super().__init__(message)
        self.status_code = status_code
        self.response_text = response_text



class StreamResult:
    """统一流式处理结果容器"""
    def __init__(self):
        self.content = ""       # 聚合的回复内容
        self.metadata = None    # 最终元数据
        self.files = []         # 文件资源列表
        self.thoughts = []      # 智能助手思考链
        self.current_tool = {}  # 当前使用的工具信息
        self.is_agent = False   # 是否为智能助手模式



class DifyClient:
    def __init__(self, api_key: str, api_url: str = "http://localhost/v1"):
        """
        Initialize Dify API client
        
        :param api_key: API authentication key
        :param base_url: Base API URL (default: http://localhost/v1)
        """
        self.api_key = api_key
        self.base_url = api_url.rstrip('/')
        self.session = requests.Session()
        self.session.headers.update({
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json"
        })
    
    
    
    def send_message(
        self,
        query: str,
        response_mode: Literal["blocking", "streaming"],
        inputs: Optional[Dict[str, Any]] = None,
        conversation_id: Optional[str] = None,
        user: Optional[str] = None,
        files: Optional[List[Dict[str, str]]] = None,
        parse_stream: bool = True
    ) -> Union[Dict[str, Any], Generator[Dict[str, Any], None, None]]:
        """
        发送对话消息
        
        :param query: 用户查询
        :param response_mode: 回复模式 (阻塞或流式)
        :param inputs: 附加输入参数
        :param conversation_id: 对话ID,可选
        :param user: 用户标识
        :param files: 文件列表,可选
        :param parse_stream: 是否解析流式响应,默认True,若为False则直接返回流式响应数据
        
        :return: 阻塞模式返回响应数据,流式模式返回生成器
        """
        url = f"{self.base_url}/chat-messages"
        payload = {
            "inputs": inputs or {},
            "query": query,
            "response_mode": response_mode,
            "conversation_id": conversation_id or "",
            "user": user or "default-user",  # 添加默认用户标识
            "files": files or []
        }
        
        try:
            if response_mode == "blocking":
                return self._handle_blocking_request(url, payload)
            if parse_stream:
                return self._handle_streaming_request(url, payload)
            else:
                return self._stream_raw_response(url, payload)
            # return self._handle_streaming_request(url, payload)
        except requests.exceptions.RequestException as e:
            raise DifyAPIError(f"Request failed: {str(e)}") from e
    
    

    def _handle_blocking_request(self, url: str, payload: Dict[str, Any]) -> Dict[str, Any]:
        """处理阻塞模式请求"""
        response = self.session.post(url, json=payload)
        if not response.ok:
            raise DifyAPIError(f"API request failed: {response.text}", response.status_code)
        
        # 检查响应内容是否为空
        if not response.text.strip():
            raise DifyAPIError("Empty response received", response.status_code)
        
        try:
            return response.json()
        except json.JSONDecodeError:
            raise DifyAPIError(
                f"Failed to parse JSON response: {response.text[:100]}...",  # 只显示前100个字符
                response.status_code
            )


    
    def _handle_streaming_request(self, url: str, payload: Dict[str, Any]) -> Generator[Dict[str, Any], None, None]:
        """改进版流式请求处理"""
        with self.session.post(url, json=payload, stream=True, timeout=30) as response:
            if not response.ok:
                raise DifyAPIError(f"API request failed: {response.text}", response.status_code)
            
            result = StreamResult()
            for line in response.iter_lines(decode_unicode=True):
                event = self._parse_stream_line(line)
                event_type = event.get("event")

                # 动态检测助手类型
                if not result.is_agent and event_type in ["agent_thought", "agent_message"]:
                    result.is_agent = True

                yield from self._process_event(event, result)

    def _process_event(self, event: Dict[str, Any], result: StreamResult) -> Generator[Dict[str, Any], None, None]:
        """处理单个事件并更新结果"""
        event_type = event.get("event")
        
        # 公共事件处理
        if event_type == "message_end":
            result.metadata = event.get("metadata")
            yield {"type": "metadata", "data": result.metadata}
            return
        
        # 基础助手处理
        if not result.is_agent:
            if event_type == "message":
                result.content += event.get("answer", "")
                yield {"type": "message_chunk", "data": event.get("answer")}
            return
        
        # 智能助手处理
        if event_type == "agent_thought":
            thought_data = {
                "tool": event.get("tool"),
                "input": event.get("tool_input"),
                "observation": event.get("observation")
            }
            result.thoughts.append(thought_data)
            yield {"type": "thought", "data": thought_data}
        
        elif event_type == "agent_message":
            result.content += event.get("answer", "")
            yield {"type": "message_chunk", "data": event.get("answer")}
        
        elif event_type == "message_file":
            file_data = {
                "id": event.get("id"),
                "type": event.get("type"),
                "url": event.get("url")
            }
            result.files.append(file_data)
            yield {"type": "file", "data": file_data}
    


    @staticmethod
    def _parse_stream_line(line: str) -> Dict[str, Any]:
        """解析流式响应的单行数据"""
        try:
            # 假设流式响应以"data:"开头,后面跟着JSON数据
            if line.startswith("data:"):
                json_str = line[5:].strip()  # 移除"data:"前缀
                if json_str:  # 确保有实际内容
                    return json.loads(json_str)
            return {"raw": line}
        except json.JSONDecodeError:
            return {"error": "Invalid JSON data", "raw": line}
    
    def _stream_raw_response(self, url: str, payload: Dict[str, Any]) -> Generator[str, None, None]:
        """直接返回流式响应的原始数据,不进行解析"""
        with self.session.post(url, json=payload, stream=True, timeout=60) as response:
            if not response.ok:
                raise DifyAPIError(f"API request failed: {response.text}", response.status_code)
            
            for line in response.iter_lines(decode_unicode=True):
                yield line  # 直接返回原始数据行






# 使用示例
if __name__ == "__main__":
    # 初始化客户端
    client = DifyClient(api_key="app-************************")
    
    try:
        
        
        # 阻塞模式示例
        blocking_response = client.send_message(
            user='abc-123',
            query="What are the specs of the iPhone 13 Pro Max?",
            response_mode="blocking" # ,
            # files=[
            #     {
            #         "type": "image",
            #         "transfer_method": "remote_url",
            #         "url": "https://cloud.dify.ai/logo/logo-site.png"
            #     }
            # ]
        )
        print("Blocking Response:", blocking_response)
        
        
        # 流式模式处理示例(解析流式数据)
        stream_gen = client.send_message(
            user='abc-123',
            query="生成日系动漫女孩图片",
            response_mode="streaming"
        )
        
        full_response = ""
        for chunk in stream_gen:
            chunk_type = chunk["type"]
            
            if chunk_type == "message_chunk":
                full_response += chunk["data"]
                print(f"内容更新: {full_response}")
            
            elif chunk_type == "thought":
                print(f"思考过程: {chunk['data']}")
            
            elif chunk_type == "file":
                print(f"生成文件: {chunk['data']['url']}")
            
            elif chunk_type == "metadata":
                print(f"最终元数据: {chunk['data']}")

        # 流式模式处理示例(直接返回原始数据流)
        for raw_line in client.send_message(
            user='abc-123',
            query="请你介绍一下唐代诗人李白",
            response_mode='streaming',
            parse_stream=False
        ):
            if raw_line.startswith("data:"):
                json_str = raw_line[5:].strip()  # 移除"data:"前缀
                if json_str:  # 确保有实际内容
                    print(json.loads(json_str))
            
    except DifyAPIError as e:
        print(f"请求失败: {str(e)}")

### 各组件及其版本的功能与集成方式 #### 1. **langgenius/dify-api:0.6.6** `langgenius/dify-api:0.6.6` 是 Dify API 的核心容器镜像,提供了一个 RESTful 接口来管理 AI 应用程序的创建、训练和推理功能。它集成了多种工具支持,如搜索引擎、天气预报等[^1]。此镜像是整个系统的控制中心,负责接收外部请求并协调其他服务完成任务。 集成方式通常通过 Docker Compose 文件定义其运行环境变量和服务端口映射关系。例如: ```yaml version: '3' services: api: image: langgenius/dify-api:0.6.6 ports: - "8000:8000" environment: DATABASE_URL: postgres://user:password@db:5432/dify_db ``` --- #### 2. **postgres:15-alpine** PostgreSQL 数据库用于存储结构化数据,比如用户的配置文件、历史记录以及其他元数据信息。版本 `15-alpine` 表示 PostgreSQL 15 版本,并采用轻量级 Alpine Linux 基础镜像构建而成。该数据库对于持久保存应用状态至关重要[^3]。 为了确保高可用性和性能优化,在实际部署过程中可以考虑设置主从复制机制或者定期备份策略。以下是简单的 compose 配置片段: ```yaml db: image: postgres:15-alpine environment: POSTGRES_USER: user POSTGRES_PASSWORD: password POSTGRES_DB: dify_db volumes: - ./data:/var/lib/postgresql/data ``` --- #### 3. **redis:6-alpine** Redis 主要作为缓存层服务于高频读取操作场景下提升响应速度的任务需求。此外还可以充当消息队列角色实现异步处理逻辑。这里选用的是 Redis 6 版本搭配 alpine 发行版以减少资源消耗。 下面展示如何将其加入到 docker-compose.yml 中并与其它微服务交互: ```yaml cache: image: redis:6-alpine ports: - "6379:6379" ``` 随后可以在应用程序内部指定连接字符串指向这个实例地址。 --- #### 4. **semitechnologies/weaviate:1.19.0** Weaviate 是一种矢量搜索引擎,能够高效检索嵌入向量空间中的相似项。这使得复杂自然语言查询变得可行,从而增强了语义理解能力。在此项目里使用的特定标签号表明开发者希望锁定兼容性良好的稳定发行版而非最新边缘特性预览版。 启动 Weaviate 实例时需注意初始化参数设定以便适配目标工作负载特征: ```yaml weaviate: image: semitechnologies/weaviate:1.19.0 ports: - "8080:8080" environment: QUERY_DEFAULTS_LIMIT: 25 AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: 'true' ``` --- #### 5. **langgenius/dify-sandbox:0.1.0** `sandbox` 容器扮演着隔离测试环境的角色,允许用户在一个受控区域内尝试新想法而不会影响生产流程。尽管当前仅处于早期迭代阶段 (v0.1.0),但它已经具备基本框架用来验证概念证明型实验成果。 典型应用场景可能涉及加载定制插件模块或是调整算法超参组合等等动作。相应部分声明如下所示: ```yaml sandbox: image: langgenius/dify-sandbox:0.1.0 depends_on: - db - cache ``` 上述例子强调了依赖链条顺序的重要性——即必须等待基础支撑设施完全就绪之后再激活高级业务单元。 --- #### 6. **nginx:latest** 最后提到 Nginx 负责反向代理职责,统一入口流量分发至下游多个后端节点上执行具体事务处理活动。由于官方维护积极频繁更新补丁修复漏洞等原因,“latest” 标签代表获取最近一次发布的通用二进制包集合[^2]。 下面是关于如何配置 SSL/TLS 加密通信链路的一个简单示范脚本节选: ```nginx server { listen 443 ssl; server_name localhost; ssl_certificate /etc/nginx/ssl/cert.pem; ssl_certificate_key /etc/nginx/ssl/key.pem; location / { proxy_pass http://api:8000/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } } ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lczdyx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值