封装 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 虽然可行,但存在以下问题:
- 需要重复处理认证、请求头等基础细节
- 响应格式复杂,尤其是流式响应需要特殊处理
- 错误处理分散,难以统一管理
- 代码可读性和可维护性较差
通过封装一个专门的 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 平台的交互过程。这个封装不仅提供了简洁的接口,还处理了认证、错误管理、流式响应解析等复杂细节,让开发者可以更专注于核心业务逻辑。
这种封装方式具有以下优点:
- 统一的认证和请求配置
- 简洁的接口抽象复杂实现细节
- 集中的错误处理机制
- 支持多种响应模式灵活使用
- 良好的类型提示增强代码可读性
可以将这个封装集成到各种 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)}")