Dify异步接口调用优化实践:解决长时任务处理与网络超时问题

Dify异步接口调用优化实践:解决长时任务处理与网络超时问题

项目背景

在当前负责的AI研发试验落地项目中,我们面临一个关键的业务需求:用户需要对商品物料信息进行知识库匹配,以找到最相似的目标物料数据。该功能基于Dify平台构建工作流实现,技术实现难度适中。

技术挑战

初始方案及局限性

初期实现采用单条匹配模式,用户反馈处理效率较低。为此,我们升级为Excel批量导入并发匹配方案,但随之暴露出多个技术瓶颈:

  1. Dify平台限制
  • 接口调用超时限制
  • 循环执行次数限制
  • 网页请求超时限制
  1. 系统稳定性问题
  • 网络波动导致连接中断
  • 大数据量匹配时连接稳定性下降
  • 用户无法可靠获取处理结果

解决方案探索

虽然可以通过调整Dify配置参数缓解部分问题,但无法从根本上解决网络不稳定导致的连接中断。开放API日志查询权限让用户自行获取结果,又会带来安全管控风险。

技术实现方案

异步接口架构设计

经过技术调研,我们参考了GitHub社区的相关讨论,发现官方暂未正式支持异步接口。基于一个社区贡献的PR思路,我们在Dify 1.8.1版本上实现了异步接口功能。

核心代码实现

1. 接口参数扩展

Service API 配置 (api/controllers/service_api/init.py):

python 复制代码
from flask import Blueprint
from flask_restx import Namespace

from libs.external_api import ExternalApi

bp = Blueprint("service_api", __name__, url_prefix="/v1")

api = ExternalApi(
    bp,
    version="1.0",
    title="Service API",
    description="API for application services",
    doc="/docs",  # Enable Swagger UI at /v1/docs
)

service_api_ns = Namespace("service_api", description="Service operations", path="/")

from . import index
from .app import annotation, app, audio, completion, conversation, file, file_preview, message, site, workflow, workflow_run
from .dataset import dataset, document, hit_testing, metadata, segment, upload_file
from .workspace import models

api.add_namespace(service_api_ns)

Completion API 异步参数 (api/controllers/service_api/app/completion.py):

python 复制代码
chat_parser = reqparse.RequestParser()
chat_parser.add_argument("inputs", type=dict, required=True, location="json", help="Input parameters for chat")
chat_parser.add_argument("query", type=str, required=True, location="json", help="The chat query")
chat_parser.add_argument("files", type=list, required=False, location="json", help="List of file attachments")
chat_parser.add_argument(
    "response_mode", type=str, choices=["blocking", "streaming"], location="json", help="Response mode"
)
chat_parser.add_argument("conversation_id", type=uuid_value, location="json", help="Existing conversation ID")
chat_parser.add_argument(
    "retriever_from", type=str, required=False, default="dev", location="json", help="Retriever source"
)
chat_parser.add_argument(
    "auto_generate_name",
    type=bool,
    required=False,
    default=True,
    location="json",
    help="Auto generate conversation name",
)
# 新增异步调用参数
chat_parser.add_argument("is_async", type=bool, required=False, default=False, location="json")
chat_parser.add_argument("workflow_id", type=str, required=False, location="json", help="Workflow ID for advanced chat")

Workflow API 异步参数 (api/controllers/service_api/app/workflow.py):

python 复制代码
workflow_run_parser = reqparse.RequestParser()
workflow_run_parser.add_argument("inputs", type=dict, required=True, nullable=False, location="json")
workflow_run_parser.add_argument("files", type=list, required=False, location="json")
workflow_run_parser.add_argument("response_mode", type=str, choices=["blocking", "streaming"], location="json")
# 新增异步调用参数
workflow_run_parser.add_argument("is_async", type=bool, required=False, default=False, location="json")
2. 应用生成实体配置

Advanced Chat 应用生成器 (api/core/app/apps/advanced_chat/app_generator.py):

python 复制代码
def generate(self, args: dict) -> Union[ChatbotAppBlockingResponse, Generator[ChatbotAppStreamResponse, None, None]]:
    # ... 原有代码 ...
    
    extras = {
        "auto_generate_conversation_name": args.get("auto_generate_name", False),
        # 新增异步配置
        "is_async": args.get("is_async", False),
    }
    
    # ... 后续处理逻辑 ...

Workflow 应用生成器 (api/core/app/apps/workflow/app_generator.py):

python 复制代码
def generate(self, args: dict) -> Union[WorkflowAppBlockingResponse, Generator[WorkflowAppStreamResponse, None, None]]:
    # ... 原有代码 ...
    
    workflow_run_id = str(uuid.uuid4())
    application_generate_entity = WorkflowAppGenerateEntity(
        task_id=str(uuid.uuid4()),
        app_config=app_config,
        file_upload_config=file_extra_config,
        inputs=self._prepare_user_inputs(
            user_inputs=inputs,
            variables=app_config.variables,
            tenant_id=app_model.tenant_id,
            strict_type_validation=True if invoke_from == InvokeFrom.SERVICE_API else False,
        ),
        files=list(system_files),
        user_id=user.id,
        stream=streaming,
        invoke_from=invoke_from,
        call_depth=call_depth,
        trace_manager=trace_manager,
        workflow_execution_id=workflow_run_id,
        # 新增异步配置
        extras={"is_async": args.get("is_async", False)},
    )
3. 任务管道处理核心逻辑

Advanced Chat 任务管道 (api/core/app/apps/advanced_chat/generate_task_pipeline.py):

python 复制代码
def _to_blocking_response(self, generator: Generator[StreamResponse, None, None]) -> ChatbotAppBlockingResponse:
    """
    处理阻塞响应
    """
    is_async = self._application_generate_entity.extras.get("is_async", False)

    for stream_response in generator:
        if is_async:
            # 异步模式立即返回任务ID
            return ChatbotAppBlockingResponse(
                task_id=stream_response.task_id,
                data=ChatbotAppBlockingResponse.Data(
                    id=self._message_id,
                    mode=self._conversation_mode,
                    conversation_id=self._conversation_id,
                    message_id=self._message_id,
                    answer=str(self._application_generate_entity.workflow_run_id),
                    created_at=int(self._message_created_at),
                ),
            )
        if isinstance(stream_response, ErrorStreamResponse):
            raise stream_response.err
        elif isinstance(stream_response, MessageEndStreamResponse):
            # 同步模式正常处理完成响应
            extras = {}
            if stream_response.metadata:
                extras["metadata"] = stream_response.metadata

            return ChatbotAppBlockingResponse(
                task_id=stream_response.task_id,
                data=ChatbotAppBlockingResponse.Data(
                    id=self._message_id,
                    mode=self._conversation_mode,
                    conversation_id=self._conversation_id,
                    message_id=self._message_id,
                    answer=self._task_state.answer,
                    created_at=self._message_created_at,
                    **extras,
                ),
            )
        else:
            continue

    raise ValueError("queue listening stopped unexpectedly.")

def _async_process_stream_response(self, publisher):
    """
    异步处理流响应 - 核心异步逻辑
    """
    # 初始化队列管理器
    queue_manager = MessageBasedAppQueueManager(
        task_id=self._application_generate_entity.task_id,
        user_id=self._application_generate_entity.user_id,
        invoke_from=self._application_generate_entity.invoke_from,
        conversation_id=self._conversation_id,
        app_mode=self._conversation_mode,
        message_id=self._message_id,
    )
    
    # 启动工作线程处理实际任务
    worker_thread = threading.Thread(
        target=self._generate_worker,
        kwargs={
            "flask_app": current_app._get_current_object(),
            "queue_manager": queue_manager,
            "context": contextvars.copy_context(),
            "publisher": publisher,
        },
    )
    worker_thread.start()

    # 返回消费者工作器的结果
    yield from self._consumer_worker(queue_manager)

def _generate_worker(self, flask_app: Flask, queue_manager: AppQueueManager, context: contextvars.Context, publisher) -> None:
    """
    在工作线程中执行生成任务
    """
    for var, val in context.items():
        var.set(val)
    with flask_app.app_context():
        response_generator = self._sync_process_stream_response(publisher)
        for generator in response_generator:
            if generator is None:
                continue
            # 将流响应映射为队列消息并发布
            message = ForwardQueueMessage(
                event=advance_chat_queue_task_map[generator.event], 
                response=generator
            )
            queue_manager.publish(message, PublishFrom.TASK_PIPELINE)

def _consumer_worker(self, queue_manager: AppQueueManager) -> Generator[StreamResponse, None, None]:
    """
    消费队列中的消息
    """
    for message in queue_manager.listen():
        event = message.event
        if isinstance(event, ForwardQueueMessage):
            yield event.response

Workflow 任务管道 (api/core/app/apps/workflow/generate_task_pipeline.py):

python 复制代码
def _to_blocking_response(self, generator: Generator[StreamResponse, None, None]) -> WorkflowAppBlockingResponse:
    """
    转换为阻塞响应
    """
    is_async = self._application_generate_entity.extras.get("is_async", False)
    for stream_response in generator:
        if is_async:
            # 异步模式立即返回工作流运行ID
            return WorkflowAppBlockingResponse(
                task_id=self._application_generate_entity.task_id,
                workflow_run_id=self._application_generate_entity.workflow_execution_id,
                data=WorkflowAppBlockingResponse.Data(
                    id=self._application_generate_entity.app_config.app_id,
                    workflow_id=self._workflow_id,
                    status="processing",
                    elapsed_time=0,
                    total_tokens=0,
                    total_steps=0,
                    created_at=int(time.time()),
                    finished_at=int(time.time()),
                ),
            )
        if isinstance(stream_response, ErrorStreamResponse):
            raise stream_response.err
        elif isinstance(stream_response, WorkflowFinishStreamResponse):
            # 同步模式返回完整结果
            response = WorkflowAppBlockingResponse(
                task_id=self._application_generate_entity.task_id,
                workflow_run_id=stream_response.data.id,
                data=WorkflowAppBlockingResponse.Data(
                    id=stream_response.data.id,
                    workflow_id=stream_response.data.workflow_id,
                    status=stream_response.data.status,
                    outputs=stream_response.data.outputs,
                    error=stream_response.data.error,
                    elapsed_time=stream_response.data.elapsed_time,
                    total_tokens=stream_response.data.total_tokens,
                    total_steps=stream_response.data.total_steps,
                    created_at=int(stream_response.data.created_at),
                    finished_at=int(stream_response.data.finished_at),
                ),
            )
            return response
        else:
            continue
    raise ValueError("queue listening stopped unexpectedly.")
4. 事件映射系统

队列任务桥接 (api/core/app/entities/queue_task_bridge.py):

python 复制代码
from core.app.entities.queue_entities import AppQueueEvent, QueueEvent
from core.app.entities.task_entities import StreamEvent, StreamResponse

# 工作流事件映射
workflow_queue_task_map = {
    StreamEvent.PING: QueueEvent.PING,
    StreamEvent.ERROR: QueueEvent.ERROR,
    StreamEvent.MESSAGE: QueueEvent.TEXT_CHUNK,
    StreamEvent.MESSAGE_END: QueueEvent.MESSAGE_END,
    StreamEvent.TTS_MESSAGE: QueueEvent.TEXT_CHUNK,
    StreamEvent.TTS_MESSAGE_END: QueueEvent.MESSAGE_END,
    StreamEvent.MESSAGE_FILE: QueueEvent.MESSAGE_FILE,
    StreamEvent.MESSAGE_REPLACE: QueueEvent.MESSAGE_REPLACE,
    StreamEvent.AGENT_THOUGHT: QueueEvent.AGENT_THOUGHT,
    StreamEvent.AGENT_MESSAGE: QueueEvent.AGENT_MESSAGE,
    StreamEvent.WORKFLOW_STARTED: QueueEvent.WORKFLOW_STARTED,
    StreamEvent.WORKFLOW_FINISHED: QueueEvent.WORKFLOW_SUCCEEDED,
    StreamEvent.NODE_STARTED: QueueEvent.NODE_STARTED,
    StreamEvent.NODE_FINISHED: QueueEvent.NODE_SUCCEEDED,
    StreamEvent.NODE_RETRY: QueueEvent.RETRY,
    StreamEvent.PARALLEL_BRANCH_STARTED: QueueEvent.PARALLEL_BRANCH_RUN_STARTED,
    StreamEvent.PARALLEL_BRANCH_FINISHED: QueueEvent.PARALLEL_BRANCH_RUN_SUCCEEDED,
    StreamEvent.ITERATION_STARTED: QueueEvent.ITERATION_START,
    StreamEvent.ITERATION_NEXT: QueueEvent.ITERATION_NEXT,
    StreamEvent.ITERATION_COMPLETED: QueueEvent.ITERATION_COMPLETED,
    StreamEvent.LOOP_STARTED: QueueEvent.LOOP_START,
    StreamEvent.LOOP_NEXT: QueueEvent.LOOP_NEXT,
    StreamEvent.LOOP_COMPLETED: QueueEvent.LOOP_COMPLETED,
    StreamEvent.TEXT_CHUNK: QueueEvent.TEXT_CHUNK,
    StreamEvent.TEXT_REPLACE: QueueEvent.MESSAGE_REPLACE,
    StreamEvent.AGENT_LOG: QueueEvent.AGENT_LOG,
}

# 高级聊天事件映射
advance_chat_queue_task_map = {
    StreamEvent.PING: QueueEvent.PING,
    StreamEvent.ERROR: QueueEvent.ERROR,
    StreamEvent.MESSAGE: QueueEvent.TEXT_CHUNK,
    StreamEvent.MESSAGE_END: QueueEvent.ADVANCED_CHAT_MESSAGE_END,
    StreamEvent.TTS_MESSAGE: QueueEvent.TEXT_CHUNK,
    StreamEvent.TTS_MESSAGE_END: QueueEvent.MESSAGE_END,
    StreamEvent.MESSAGE_FILE: QueueEvent.MESSAGE_FILE,
    StreamEvent.MESSAGE_REPLACE: QueueEvent.MESSAGE_REPLACE,
    StreamEvent.AGENT_THOUGHT: QueueEvent.AGENT_THOUGHT,
    StreamEvent.AGENT_MESSAGE: QueueEvent.AGENT_MESSAGE,
    StreamEvent.WORKFLOW_STARTED: QueueEvent.WORKFLOW_STARTED,
    StreamEvent.WORKFLOW_FINISHED: QueueEvent.WORKFLOW_SUCCEEDED,
    StreamEvent.NODE_STARTED: QueueEvent.NODE_STARTED,
    StreamEvent.NODE_FINISHED: QueueEvent.NODE_SUCCEEDED,
    StreamEvent.NODE_RETRY: QueueEvent.RETRY,
    StreamEvent.PARALLEL_BRANCH_STARTED: QueueEvent.PARALLEL_BRANCH_RUN_STARTED,
    StreamEvent.PARALLEL_BRANCH_FINISHED: QueueEvent.PARALLEL_BRANCH_RUN_SUCCEEDED,
    StreamEvent.ITERATION_STARTED: QueueEvent.ITERATION_START,
    StreamEvent.ITERATION_NEXT: QueueEvent.ITERATION_NEXT,
    StreamEvent.ITERATION_COMPLETED: QueueEvent.ITERATION_COMPLETED,
    StreamEvent.LOOP_STARTED: QueueEvent.LOOP_START,
    StreamEvent.LOOP_NEXT: QueueEvent.LOOP_NEXT,
    StreamEvent.LOOP_COMPLETED: QueueEvent.LOOP_COMPLETED,
    StreamEvent.TEXT_CHUNK: QueueEvent.TEXT_CHUNK,
    StreamEvent.TEXT_REPLACE: QueueEvent.MESSAGE_REPLACE,
    StreamEvent.AGENT_LOG: QueueEvent.AGENT_LOG,
}

class ForwardQueueMessage(AppQueueEvent):
    """
    转发队列消息实体
    """
    event: QueueEvent = QueueEvent.PING
    response: StreamResponse
5. 结果查询接口

消息查询API (api/controllers/web/message.py):

python 复制代码
class MessageApi(WebApiResource):
    @marshal_with(message_fields)
    def get(self, app_model: App, end_user: EndUser, message_id):
        message_id = str(message_id)

        message = db.session.query(Message).filter(
            Message.id == message_id, 
            Message.app_id == app_model.id
        ).first()

        if not message:
            raise NotFound("Message Not Exists.")

        return message

# API路由注册
api.add_resource(MessageListApi, "/messages")
api.add_resource(MessageFeedbackApi, "/messages/<uuid:message_id>/feedbacks")
api.add_resource(MessageMoreLikeThisApi, "/messages/<uuid:message_id>/more-like-this")
api.add_resource(MessageSuggestedQuestionApi, "/messages/<uuid:message_id>/suggested-questions")
api.add_resource(MessageApi, "/apps/messages/<uuid:message_id>")

最终效果




技术总结

本次优化通过引入异步处理机制,有效解决了Dify平台在处理批量数据匹配时的技术瓶颈。关键成功因素包括:

  1. 非侵入式设计:在保持原有接口兼容性的基础上扩展功能
  2. 队列管理:基于消息队列实现任务状态管理
  3. 线程安全:多线程环境下的数据一致性保障
  4. 错误恢复:完善的异常处理和任务恢复机制
    该方案为类似AI应用场景中的长时任务处理提供了可复用的技术范式,在提升系统稳定性的同时,显著改善了用户体验。

注:本文基于Dify 1.8.1版本实现,具体实现细节可能随版本更新而调整。

相关推荐
海森大数据3 小时前
AI突破“化学空间困境”:一场药物设计的范式革命
人工智能·语言模型
nkwshuyi3 小时前
ChatGPT Atlas Agent 如何帮你采集数据和调研?
人工智能·chatgpt
buttonupAI3 小时前
今日Reddit AI高价值讨论分析 - 10.27
人工智能·chatgpt
Hs_QY_FX3 小时前
幸福指数数据分析与预测:从数据预处理到模型构建完整案例
开发语言·python·机器学习
虫无涯3 小时前
解锁 Playwright 自动化测试:一篇教程入门WebUI自动化测试【入门级】
python·单元测试·测试
rengang663 小时前
020-Spring AI Alibaba DashScope Image 功能完整案例
java·人工智能·spring·spring ai·ai应用编程
rengang663 小时前
AI驱动的DevOps:AI大模型自动化部署、监控和运维流程
运维·人工智能·ai编程·devops·ai智能体编程
碧海银沙音频科技研究院3 小时前
ES7243E ADC模拟音频转i2S到 BES I2S1 Master输出播放到SPK精准分析
人工智能·算法·音视频
音视频牛哥3 小时前
从“十五五”规划看中国视频基础设施的下一个五年:SmartMediaKit 的战略跃迁与时代机遇
人工智能·音视频·大牛直播sdk·十五五规划具身智能·十五五规划音视频·低空经济低延迟音视频方案·具身智能rtsp rtmp