【Dify】使用 python 调用 Dify 的 API 服务,查看“知识检索”返回内容,用于前端溯源展示

本文介绍了如何使用Dify HTTP API实现聊天问答功能,支持文本和图文交互。主要包含三个核心接口:上传文件获取ID、发送聊天消息(可携带图片)和删除会话。

脚本提供了极简封装类DifyChat,包含安全响应解析和可选会话管理功能。使用时需配置API地址、密钥和用户标识,支持纯文本问答和图文问答两种模式,并详细说明了流式输出、多用户适配等扩展场景的实现方法。

参考链接:对接Dify的api接口 上传文件、发起对话、删除对话

一、Dify 聊天示例脚本说明

本脚本演示了如何通过 Dify HTTP API 进行聊天问答,并可选携带图片。核心流程:

  1. 上传文件(可选)

    • 调用 /v1/files/upload 上传本地图片,得到 upload_file_id
    • 只有在需要图文问答时才上传;纯文本时可跳过。
  2. 发送对话消息

    • 调用 /v1/chat-messages,必填 query,可选传 files(当有 upload_file_id 时)。
    • response_mode = "blocking" 表示同步等待完整答案;如需流式可改为 "streaming" 并按 SSE 解析。
  3. 删除会话(可选)

    • 调用 DELETE /v1/conversations/{id};返回可能是 204 空 body,因此需要容错解析。

1.1、代码结构

  • DifyChat:极简封装,包含 upload_filesenddeletecreate_chat(可选)。
  • _safe_parse_response:统一解析响应,优先尝试 JSON;否则返回状态码和文本,避免 JSONDecodeError
  • 示例入口:根据 image_path 是否存在,选择纯文本或图文问答,并打印答案。

1.2、快速使用

  1. 配置:

    • api_url: 你的 Dify 服务地址(到 /v1)。
    • api_key: Dify 应用的 API Key(通常以 app- 开头)。
    • user: 请求用户标识,用于配额和会话归属。
  2. 纯文本问答:

    python 复制代码
    chat = DifyChat(api_url, api_key)
    ret_send = chat.send("你的问题")
    print(ret_send.get("answer"))
  3. 图文问答:

    python 复制代码
    ret_upload = chat.upload_file("xxx.jpg")
    file_id = ret_upload["id"]
    ret_send = chat.send("你的问题", file_id=file_id)
    print(ret_send.get("answer"))
  4. 可选删除会话:

    python 复制代码
    if ret_send.get("conversation_id"):
        ret_delete = chat.delete(ret_send["conversation_id"])
        print(ret_delete)

1.3、注意事项

  • files 字段仅在传入 file_id 时添加,允许无图时直接问答。
  • upload_file 使用 with open 避免句柄泄漏,并在 multipart 表单里传 user
  • delete 可能返回 204/空 body,必须使用安全解析。
  • 示例里用 ret_sendret_delete 避免变量覆盖导致拿不到 answer
  • 若要流式输出,将 response_mode 改为 "streaming" 并按 SSE 处理返回。

1.4、适配其他项目的改动指引

  • 如需在多线程/多协程环境复用:将会话 id 存储在线程上下文或显式传入。
  • 若有多用户:实例化时传入不同 user 值;或在方法参数增加 user 覆盖。
  • 如需支持多文件:在 send 中把 data["files"] 扩展为文件列表,并按 Dify 要求传 type/transfer_method/upload_file_id
  • 若要改为流式:封装一个 send_stream,使用 stream=True 并消费 SSE 事件。

二、代码部分

python 复制代码
import os.path

import requests


class DifyChat:
	"""
	一个极简的 Dify HTTP API 调用封装(适合快速验证流程)。

	你这里用到的核心接口:
	- POST /v1/chat-messages     发起一次对话消息(可选携带图片)
	- POST /v1/files/upload      上传文件,得到 upload_file_id(给 chat-messages 的 files 用)
	- DELETE /v1/conversations/{id}  删除会话(有的服务端会返回空 body)
	"""

	def __init__(self, api_url, api_key, user="abc-123"):
		"""
		:param api_url: Dify 服务地址(到 /v1 这一层),例如 http://10.0.100.98/v1
		:param api_key: Dify 应用 API Key(通常以 app- 开头)
		:param user: 用于标识请求用户的字符串。Dify 多数接口需要这个字段用于配额/会话归属。
		"""
		self.api_url = api_url
		self.api_key = api_key
		self.user = user

		# JSON 请求通用 Header(upload_file 是 multipart,会单独传 headers)
		self.headers = {
			'Authorization': f'Bearer {self.api_key}',
			'Content-Type': 'application/json'
		}

	@staticmethod
	def _safe_parse_response(response):
		"""
		尽量把响应解析成 JSON。
		- 像 DELETE 这类接口,经常返回 204 No Content(空 body)
		- 或者返回纯文本/HTML 错误页(不是 JSON)

		因此这里做"兜底",避免直接 response.json() 抛 JSONDecodeError。
		"""
		try:
			# 204/205 等可能无 body
			if response.status_code in (204, 205) or not response.content:
				return {"status_code": response.status_code, "ok": response.ok, "text": ""}
			return response.json()
		except Exception:
			return {
				"status_code": response.status_code,
				"ok": response.ok,
				"text": response.text,
			}

	def create_chat(self, chat_name):
		"""
		创建 chat(是否需要取决于你的 Dify 应用/版本;很多情况下直接用 chat-messages 即可)。
		"""
		url = f'{self.api_url}/chats/create'
		data = {'chat_name': chat_name}
		response = requests.post(url, json=data, headers=self.headers)
		return self._safe_parse_response(response)

	def send(self, message, file_id=None, conversation_id=""):
		"""
		发送一条消息(文本问答),可选带图片。

		:param message: 用户问题/提示词(query)
		:param file_id: upload_file 接口返回的 id(即 upload_file_id);不传则表示纯文本问答
		:param conversation_id: 继续同一个会话时传入;留空表示开启新会话
		"""
		url = f'{self.api_url}/chat-messages'

		data = {
			# inputs: 给"工作流应用/带变量的应用"传入输入变量;纯聊天一般为空即可
			"inputs": {},
			# query: 你的提问文本
			"query": message,
			# blocking: 同步等待完整结果;如果你要流式输出,可改为 streaming(并按 SSE 解析)
			"response_mode": "blocking",
			# conversation_id: 传入就续聊,不传就新开一轮会话
			"conversation_id": conversation_id or "",
			# user: 用于标识请求用户
			"user": self.user
		}

		# 只有在传入 file_id 时才携带 files 字段(不传图就纯文本问答)
		# files 的含义:告诉 Dify"本次对话引用了哪些已上传文件"。
		if file_id:
			data["files"] = [{
				# type: 文件类型,这里是 image
				"type": "image",
				# transfer_method: local_file 表示使用"先上传 /files/upload,再在此引用 upload_file_id"的方式
				"transfer_method": "local_file",
				# upload_file_id: /files/upload 返回的 id
				"upload_file_id": file_id
			}]

		response = requests.post(url, json=data, headers=self.headers)
		return self._safe_parse_response(response)

	def delete(self, conver_id):
		"""
		删除会话。
		注意:服务端可能返回 204(空响应体),所以不能直接 response.json()。
		"""
		url = f'{self.api_url}/conversations/{conver_id}'
		data = {"user": self.user}
		response = requests.delete(url, json=data, headers=self.headers)
		return self._safe_parse_response(response)

	def upload_file(self, file_path):
		"""
		上传本地文件到 Dify,返回值中通常包含:{"id": "..."}。
		随后把这个 id 作为 send(..., file_id=...) 的 upload_file_id 来引用。
		"""
		url = f'{self.api_url}/files/upload'
		# Dify 通常要求 multipart form 里带 user 字段
		data = {"user": self.user}
		headers = {'Authorization': f'Bearer {self.api_key}'}
		with open(file_path, 'rb') as f:
			files = {'file': ("a.jpg", f, 'image/jpg')}
			response = requests.post(url, files=files, data=data, headers=headers)
		return self._safe_parse_response(response)


# 示例
if __name__ == "__main__":
	# 你的 Dify 服务地址(到 /v1)
	api_url = "http://127.0.0.1/v1"
	# 你的应用 API Key
	api_key = "app-xxxxxxxxxxxxxxxxxxxx"
	# 你的提问
	query = "时空之战的主要人物有哪些?"

	# image_path 为空字符串或不存在文件时:纯文本问答
	# image_path 指向真实图片文件时:会先上传图片,再在对话里引用该图片
	image_path = ""
	# image_path = "./beiguangdeshudianta1.jpg"

	chat = DifyChat(api_url, api_key)

	if not os.path.exists(image_path):
		# 方式1:不上传图片,纯文本问答
		ret_send = chat.send(query)
	else:
		# 方式2:需要图片时才上传,并把 file_id 带到 send 里
		ret_upload = chat.upload_file(image_path)
		file_id = ret_upload["id"]
		ret_send = chat.send(query, file_id=file_id)

	# send 的返回里一般会有 message_id / conversation_id / answer 等字段
	message_id = ret_send.get("message_id")
	conver_id = ret_send.get("conversation_id")

	print(ret_send)
	# 如果你的应用正常返回 answer,这里会打印答案
	if isinstance(ret_send, dict) and "answer" in ret_send:
		print(ret_send["answer"])

	# 可选:删除会话(有的场景你不需要删,保留会话便于续聊)
	# 注意:delete 的返回可能是 204 空 body,因此 ret_delete 不一定包含 JSON 字段
	if conver_id:
		ret_delete = chat.delete(conver_id)
		print(ret_delete)

api_key 通过这里获取

三、ret 返回内容

ret_send 的返回全部内容,如下

json 复制代码
{
  'event': 'message',
  'task_id': 'e50a0154-5523-4c1a-93de-ae45a2568d00',
  'id': 'c3f11257-cc16-4718-84f8-f5a550b38757',
  'message_id': 'c3f11257-cc16-4718-84f8-f5a550b38757',
  'conversation_id': '3a1f9dd3-97a0-4d6e-bf74-bae87754ab02',
  'mode': 'advanced-chat',
  'answer': '根据提供的知识内容,时空之战的主要人物包括:\n\n1. 大雄:故事的主角,原本性格懦弱、逃避责任,但在经历未来世界的战斗后实现内心成长。\n2. 哆啦A梦:大雄的好朋友,来自未来的机器猫,拥有众多神奇道具,是事件的关键推动者。\n3. 特兰克斯:来自未来的超级赛亚人,身披战甲,拥有强大战斗力,为拯救未来世界而寻求帮助。\n',
  'metadata': {
    'retriever_resources': [{
      'position': 1,
      'dataset_id': '04adf974-e176-4d80-8d56-9720718b2b37',
      'dataset_name': '时空之战_1',
      'document_id': '时空之战.md',
      'document_name': '时空之战.md',
      'data_source_type': None,
      'segment_id': None,
      'retriever_from': 'workflow',
      'score': 0.36003251935408387,
      'hit_count': None,
      'word_count': None,
      'segment_position': None,
      'index_node_hash': None,
      'content': '# 哆啦A梦与超级赛亚人:时空之战\n\n在一个寻常的午后,大雄依旧坐在书桌前发呆,作业堆得像山,连第一页都没动。哆啦A梦在一旁翻着漫画,时不时叹口气,觉得这孩子还是一如既往的不靠谱。正当他们的生活照常进行时,一道强光突然从天而降,整个房间震动不已。光芒中走出一名金发少年,身披战甲、气势惊人,他就是来自未来的超级赛亚人------特兰克斯。他一出现便说出了惊人的话:未来的地球即将被黑暗势力摧毁,他来此是为了寻求哆啦A梦的帮助。\n\n哆啦A梦与大雄听后大惊,但也从特兰克斯坚定的眼神中读出了不容拒绝的决心。特兰克斯解释说,未来的敌人并非普通反派,而是一个名叫"黑暗赛亚人"的存在,他由邪恶科学家复制了贝吉塔的基因并加以改造,实力超乎想象。这个敌人不仅拥有赛亚人战斗力,还能操纵扭曲的时间能量,几乎无人可敌。特兰克斯已经独自战斗多年,但每一次都以惨败告终。他说:"科技,是我那个时代唯一缺失的武器,而你们,正好拥有它。"\n\n于是,哆啦A梦带着特兰克斯与大雄启动时光机,穿越到了那个即将崩溃的未来世界。眼前的景象令人震撼:城市沦为废墟,大地裂痕纵横,天空中浮动着压抑的黑雾。特兰克斯说,这正是黑暗赛亚人带来的结果,一切生命几乎都被抹杀,只剩他在苦苦支撑。大雄虽感到恐惧,但看到无辜的人类遭殃,内心逐渐燃起斗志。哆啦A梦则冷静地分析局势,决定使用他最强的三样秘密道具来对抗黑暗势力。\n\n三件秘密道具分别是:可以临时赋予超级战力的"复制斗篷",能暂停时间五秒的"时间停止手表",以及可在一分钟中完成一年修行的"精神与时光屋便携版"。大雄被推进精神屋内,在其中接受密集的训练,虽然只有几分钟现实时间,他却经历了整整一年的苦修。刚开始他依旧软弱,想放弃、想逃跑,但当他想起静香、父母,还有哆啦A梦那坚定的眼神时,他终于咬牙坚持了下来。出来之后,他的身体与精神都焕然一新,眼神中多了一份成熟与自信。\n\n最终战在黑暗赛亚人的空中要塞前爆发,特兰克斯率先出击,释放全力与敌人正面对决。哆啦A梦则用任意门和道具支援,从各个方向制造混乱,尽量压制敌人的时空能力。但黑暗赛亚人太过强大,仅凭特兰克斯一人根本无法压制,更别说击败。就在特兰克斯即将被击倒之际,大雄披上复制斗篷、冲破恐惧从高空跃下。他的拳头燃烧着金色光焰,目标直指敌人心脏。\n\n时间停止装置在关键时刻启动,世界陷入静止,大雄用这个短短五秒接近了敌人的盲点。他集中全力,一记重拳击穿了黑暗赛亚人的能量核心,引发巨大的能量反冲。黑暗赛亚人尖叫着化为碎光,天空中的黑雾瞬间散去,阳光重新洒落大地。特兰克斯倒在地上,看着眼前这个曾经懦弱的少年,露出了欣慰的笑容。他知道,这一次,是大雄救了世界。\n\n战后,未来世界开始恢复,植物重新生长,人类重建家园。特兰克斯告别时紧紧握住大雄的手,说:"你是我见过最特别的战士。"哆啦A梦也为大雄感到骄傲,说他终于真正成长了一次。三人站在山丘上,看着远方重新明亮的地平线,心中感受到从未有过的安宁。随后,哆啦A梦与大雄乘坐时光机返回了属于他们的那个年代,一切仿佛又恢复平静。\n\n回到现代后,大雄仿佛变了一个人,不再轻易抱怨、不再逃避责任。他认真写完作业,帮妈妈买菜,甚至主动练习体育,哆啦A梦惊讶得说不出话来。他知道,这不是一时兴起,而是大雄真正内心成长的结果。大雄有时会望着天空出神,仿佛还能看见未来世界的那一片废墟与重生的希望。他不会说出来,但他心中永远铭记那一战。\n\n几天后,电视新闻中突然出现一则画面:一位金发少年在街头击退了失控的机器人,引发市民围观与猜测。大雄放下手中的课本,望向哆啦A梦,两人心照不宣地笑了。也许,特兰克斯又回来了,也许,新的敌人正在逼近。冒险从未真正结束,而他们,早已准备好了。无论时空如何动荡,他们将永远并肩作战。\n',
      'page': None,
      'doc_metadata': None
    }, {
      'position': 2,
      'dataset_id': '2030ad1d-3b1e-410a-8745-168a393a678f',
      'dataset_name': '博士论文知识库',
      'document_id': '配电网单相断线故障选线与定位方法的研究_张晓文.pdf',
      'document_name': '配电网单相断线故障选线与定位方法的研究_张晓文.pdf',
      'data_source_type': None,
      'segment_id': None,
      'retriever_from': 'workflow',
      'score': 0.34555257968591596,
      'hit_count': None,
      'word_count': None,
      'segment_position': None,
      'index_node_hash': None,
      'content': '障定位方法,首先利用对称分量法,建立小电流接地系统单相断线接地复合故障的复合序网,分析不同故障类型零序电压的变化特征,根据零序电压幅值的差异区分单相断线电源侧接地故障和单相断线负荷侧接地故障。同时为了区分单相断线不接地故障与单相断线接地复合故障,按照躲过单相断线不接地时的最大零序电压对判据的启动值进行整定。该方法适用于中性点不接地系统或经消弧线圈接地的配电网,具有较高的可靠性。文献[100]利用序网络分析了单相断线故障发生后电源侧相电压、电流的特征,提出了基于相电流的故障区段定位方法,但是未考虑接地过渡电阻的大小。针对配电网谐振接地系统,文献[101-103]在建立的单相断线故障模型的基础上,分析了故障点上下游的相电压、线电压和中性点电压的变化,总结了谐振接地系统发生单相断线故障后各电压变化的规律,为谐振接地系统单相断线故障定位提供了理论依据。(3)人工智能基于人工智能算法较多应用在配电网接地故障定位,在单相断线故障定位的应用较少。文献[104]基于配电网的多源数据提出了改进决策树的分支断线故障定位方法。该方法根据数据间的关联规则对数据进行分析,利用决策树计算出故障特征信息,并与关联规则进行匹配,确定故障位置。文献[105]同样也建立了兼具电气量、时空量特征的断线故障诊断特征库,通过最小冗余和最大相关算法筛选出主要特征,利用频繁模式增长算法挖掘出主要特征与结果特征之间的关联关系,进而确定故障区段。同样,文献[106]利用可视化来显示断线故障发生后电气量数据集的内部关系,然后使用关联规则挖掘找到与故障特征信息相关的因素,并将卡方作为相关性的度量,筛选出主要特征。根据各线路特征之间的关联关系,确定故障区段。该方法从多源数据中挖掘出有用的信息,具有较好的容错性,但是网络一旦发生变化,需要重新挖掘新的规则,使计算量变大。目前,大多数机器学习方法都是基于向量数据的,对于多维数据,必须将张量数据转换成向量数据。而多维数据向量化后,会破坏数据原有的结构信息,且还容易导致较高的计算量。另一方面,在解决实际问题时,由于各场景的数据千差万别,模型的配置没有统一的标准,很难最大程度发挥算法的优势。',
      'page': None,
      'doc_metadata': None
    }, {
      'position': 3,
      'dataset_id': '2030ad1d-3b1e-410a-8745-168a393a678f',
      'dataset_name': '单相博士论文知识库',
      'document_id': '配电网单相断线故障选线与定位方法的研究_张晓文.pdf',
      'document_name': '配电网单相断线故障选线与定位方法的研究_张晓文.pdf',
      'data_source_type': None,
      'segment_id': None,
      'retriever_from': 'workflow',
      'score': 0.1545743801846572,
      'hit_count': None,
      'word_count': None,
      'segment_position': None,
      'index_node_hash': None,
      'content': '第3 章基于Hausdorff 距离的单相断线故障选线方法在农村及偏远的乡镇,配电线路主要是以架空线路为主,受环境、外力等因素的影响,配电线路容易断开。断线故障一旦发生后,会引发大面积的连电事故,还可能会中断电能的传输。因此,本章针对架空线路提出了一种单相断线故障选线方法。上一章对中性点不接地系统发生单相断线故障及单相断线接地复合故障进行了分析发现:故障发生后,系统中故障线路的负序电流变化明显,可以用这个特征进行故障选线。但各线路的负序电流呈现出复杂的非线性特性和非平稳性,且采集的数据具有高维时序特性。另一方面,负序电流也并非只存在于断线故障,如果直接使用各馈线的负序电流进行故障选线,极易发生误判,因此,如何从负序电流信号中提取故障特征是故障选线的关键。传统的非线性分析方法在处理数据时具有局限性,如多尺度熵在粗粒化过程中,时间序列会随着尺度因子的增加而快速缩短,导致熵值的精度低,稳定性差,且忽略信号的实际幅值意味着信号的一些信息可能被丢失[112,113]。为了解决上述问题,本章提出了基于精细时移多尺度标准差多样熵(RTSMSDDE)的负序电流特征提取方法。首先利用多样熵(DE)来描述故障信号的特征,DE与现有的熵方法不同,它利用模式相似性的统计概率来描述状态分布,可以更好地反映内部模式的变化,并能够准确地估计信号的动态复杂性[114]。其次针对DE 在多尺度环境中的缺点,引入了时移多尺度方法代替传统粗粒化过程,并使用精细化方法来避免熵值在较大尺度因子下的波动。然后,综合考虑信号的实际幅值,将时间序列的多尺度标准差作为熵值的影响权重,有效地保存了原始数据的重要信息,避免了信息丢失。最后,结合Hausdorff 距离算法衡量各馈线特征值的相似性,以实现故障选线,通过MATLAB/Simulink 仿真验证了所提方法的有效性。',
      'page': None,
      'doc_metadata': None
    }],
    'usage': {
      'prompt_tokens': 22886,
      'prompt_unit_price': '0',
      'prompt_price_unit': '0',
      'prompt_price': '0',
      'completion_tokens': 413,
      'completion_unit_price': '0',
      'completion_price_unit': '0',
      'completion_price': '0',
      'total_tokens': 23299,
      'total_price': '0',
      'currency': 'USD',
      'latency': 4.649309985339642
    }
  },
  'created_at': 1769582803
}

其中 answer 是需要展示的内容,如下:

复制代码
根据提供的知识内容,时空之战的主要人物包括:

1. 大雄:故事的主角,原本性格懦弱、逃避责任,但在经历未来世界的战斗后实现内心成长。
2. 哆啦A梦:大雄的好朋友,来自未来的机器猫,拥有众多神奇道具,是事件的关键推动者。
3. 特兰克斯:来自未来的超级赛亚人,身披战甲,拥有强大战斗力,为拯救未来世界而寻求帮助。

其中,知识检索的内容,存储在了 metadata 内,可用作前端对"知识检索"原文的追溯。

4、总结

记录过程,方便查询复用。通过这样一个简单的示例调用,展示了调用 Dify 服务的整个过程,尤其是与前后端人员的交互,清楚的知道了返回内容,便于展示需要。

相关推荐
wjhx2 小时前
在Qt Design Studio中进行页面切换
前端·javascript·qt
qq_417129252 小时前
基于C++的区块链实现
开发语言·c++·算法
霍理迪2 小时前
JS—数组
开发语言·前端·javascript
Surplusx2 小时前
运用VS Code前端开发工具完成微博发布案例
前端·html
哪里不会点哪里.2 小时前
Nginx 详解:高性能 Web 服务器与反向代理
服务器·前端·nginx
Ulyanov2 小时前
超越平面:用impress.js打造智能多面棱柱演示器
开发语言·前端·javascript·平面
2401_832402752 小时前
C++中的命令模式实战
开发语言·c++·算法
zhougl9962 小时前
Java定时任务实现
java·开发语言·python
HWL56792 小时前
vue抽离自定义指令的方法
前端·javascript·vue.js