[Python] LangGraph SSE Streaming Delay: Events Batched, Not Real-time

Discussão em 'Python' iniciado por Stack, Maio 29, 2025.

  1. Stack

    Stack Membro Participativo

    logger = setup_logger()

    async def generate_sse_event(event: str, data: Any, step: str = None) -> str: """Generate SSE formatted message with timestamp and consistent structure.""" timestamp = time.time() event_data = { "event": event, "step": step or event, "timestamp": timestamp, "status": "success" }

    if isinstance(data, (dict, list)):
    event_data["data"] = data
    else:
    event_data["data"] = str(data)

    if event == "error":
    event_data["status"] = "error"
    event_data["error_details"] = {
    "message": str(data),
    "type": type(data).__name__
    }

    return f"data: {json.dumps(event_data)}\n\n"


    async def rag_node(state: AgentState) -> AgentState: """Wrapper function for RAG processing with proper state handling.""" result = await rag_tool.ainvoke({"state": state.model_dump()}) return AgentState(**{**state.model_dump(), **result})

    async def stream_rag_events(state: AgentState) -> AsyncGenerator[str, None]: """Stream RAG events as SSE.""" try: yield await generate_sse_event("rag_start", { "message": "Starting RAG processing", "query": state.query })

    # Initial processing event
    yield await generate_sse_event("status", {"message": "Starting RAG processing", "query": state.query})

    # Cache check event
    yield await generate_sse_event("cache_check", {"message": "Checking cache for query"})

    # Get chat history if available
    if state.session_id:
    yield await generate_sse_event("history_retrieval", {
    "message": f"Retrieving chat history for session {state.session_id}"
    })

    # Personalization check
    yield await generate_sse_event("personalization_check", {
    "is_personalized": state.is_personalized,
    "entity_id": state.entity_id
    })

    # Query refinement
    yield await generate_sse_event("query_refinement_start", {"message": "Starting query refinement"})

    # Parallel processing events
    yield await generate_sse_event("parallel_processing", {
    "message": "Starting parallel processing of intent analysis and embeddings"
    })

    # Process through RAG pipeline
    result = await rag_node(state)
    result_dict = result.model_dump() # Convert AgentState to dictionary

    # Emit events for each major step
    yield await generate_sse_event("query_refinement_complete", {
    "refined_query": result_dict.get("refined_query", state.query),
    "original_query": state.query,
    "personalization_details": result_dict.get("personalization_details", {})
    })

    yield await generate_sse_event("intent_analysis_complete", {
    "intents": result_dict.get("intent_data", []),
    "entities": result_dict.get("entity_data", [])
    })

    yield await generate_sse_event("embedding_generation_complete", {
    "message": "Embeddings generated successfully"
    })

    yield await generate_sse_event("search_execution", {
    "filter_expression": result_dict.get("search_filters"),
    "message": "Executing vector search"
    })

    yield await generate_sse_event("search_results", {
    "count": len(result_dict.get("search_results", [])),
    "top_results": result_dict.get("search_results", [])[:3]
    })

    yield await generate_sse_event("response_generation", {
    "message": "Generating final response"
    })

    # Final result with all metadata
    yield await generate_sse_event("completion", {
    "response": result_dict.get("response", {}).get("message"),
    "final_response": {
    "session_id": result_dict.get("session_id"),
    "processing_time": result_dict.get("processing_details", {}).get("processing_time"),
    "refined_query": result_dict.get("refined_query"),
    "search_results_count": len(result_dict.get("search_results", []))
    }
    })

    yield await generate_sse_event("rag_complete", {
    "message": "RAG processing completed",
    "response": result.response,
    "session_id": result.session_id
    })

    except Exception as e:
    logger.error(f"RAG processing error: {str(e)}", exc_info=True)
    yield await generate_sse_event("error", {
    "message": f"RAG processing failed: {str(e)}",
    "stage": "rag_processing"
    })


    class EnhancedStateGraph(StateGraph): async def ainvoke(self, state: AgentState) -> AsyncGenerator[str, None]: """Execute the graph with SSE streaming.""" try: yield await generate_sse_event("connected", "Connection established")

    # Track processed nodes to prevent duplicates
    processed_nodes = set()

    async for node_name, node_result in self.astream(state):
    if node_name == "supervisor":
    yield await generate_sse_event("supervisor_decision", {
    "next_step": node_result.next_step,
    "reason": getattr(node_result, "reason", "No reason provided")
    })

    elif node_name == "small_talk" and "small_talk" not in processed_nodes:
    processed_nodes.add("small_talk")
    yield await generate_sse_event("small_talk_start", {
    "message": "Processing small talk request"
    })

    # Get the final response from small talk agent
    response = await small_talk_agent_node(state)
    yield await generate_sse_event("small_talk_response", {
    "response": response,
    "session_id": state.session_id
    })

    elif node_name == "rag" and "rag" not in processed_nodes:
    processed_nodes.add("rag")
    async for event in stream_rag_events(state):
    yield event

    yield await generate_sse_event("workflow_complete", {
    "message": "Workflow completed successfully",
    "processed_nodes": list(processed_nodes)
    })

    except Exception as e:
    logger.error(f"Workflow error: {str(e)}")
    yield await generate_sse_event("error", {
    "message": f"Workflow failed: {str(e)}",
    "stage": "workflow_execution"
    })


    def create_main_flow() -> EnhancedStateGraph: """Create the workflow graph with proper node connections.""" builder = EnhancedStateGraph(AgentState)

    # Add nodes
    builder.add_node("supervisor", supervisor_node)
    builder.add_node("small_talk", lambda state: state) # Pass-through, actual processing in ainvoke
    builder.add_node("rag", lambda state: state) # Pass-through, actual processing in ainvoke

    # Set entry point
    builder.set_entry_point("supervisor")

    # Add conditional edges
    builder.add_conditional_edges(
    "supervisor",
    lambda state: state.next_step,
    {
    "small_talk": "small_talk",
    "rag": "rag"
    }
    )

    # Add edges to END
    builder.add_edge("small_talk", END)
    builder.add_edge("rag", END)

    return builder.compile()


    async def execute_with_sse(state: AgentState) -> AsyncGenerator[str, None]: """Main entry point for executing the workflow with SSE.""" workflow = create_main_flow() async for event in workflow.ainvoke(state): yield event

    Continue reading...

Compartilhe esta Página