Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug]: draw_all_possible_flow returns a blank html #15935

Open
plaban1981 opened this issue Sep 10, 2024 · 26 comments
Open

[Bug]: draw_all_possible_flow returns a blank html #15935

plaban1981 opened this issue Sep 10, 2024 · 26 comments
Labels
bug Something isn't working triage Issue needs to be triaged/prioritized

Comments

@plaban1981
Copy link

Bug Description

I am trying out the example specified in https://docs.llamaindex.ai/en/stable/examples/workflow/rag/ page.
Please find my code below

from llama_index.core.workflow import Event
from llama_index.core.schema import NodeWithScore


class RetrieverEvent(Event):
    """Result of running retrieval"""

    nodes: list[NodeWithScore]


class RerankEvent(Event):
    """Result of running reranking on retrieved nodes"""

    nodes: list[NodeWithScore]

from llama_index.core import SimpleDirectoryReader, VectorStoreIndex
from llama_index.core.response_synthesizers import CompactAndRefine
from llama_index.core.postprocessor.llm_rerank import LLMRerank
from llama_index.core.workflow import (
    Context,
    Workflow,
    StartEvent,
    StopEvent,
    step,
)

from llama_index.llms.groq import Groq
from llama_index.embeddings.huggingface import HuggingFaceEmbedding

class RAGWorkflow(Workflow):
    @step
    async def ingest(self, ctx: Context, ev: StartEvent) -> StopEvent | None:
        """Entry point to ingest a document, triggered by a StartEvent with `dirname`."""
        dirname = ev.get("dirname")
        if not dirname:
            return None

        documents = SimpleDirectoryReader(dirname).load_data()
        index = VectorStoreIndex.from_documents(
            documents=documents,
            embed_model=HuggingFaceEmbedding(model_name="BAAI/bge-small-en-v1.5"),
        )
        return StopEvent(result=index)

    @step
    async def retrieve(
        self, ctx: Context, ev: StartEvent
    ) -> RetrieverEvent | None:
        "Entry point for RAG, triggered by a StartEvent with `query`."
        query = ev.get("query")
        index = ev.get("index")

        if not query:
            return None

        print(f"Query the database with: {query}")

        # store the query in the global context
        await ctx.set("query", query)

        # get the index from the global context
        if index is None:
            print("Index is empty, load some documents before querying!")
            return None

        retriever = index.as_retriever(similarity_top_k=2)
        nodes = await retriever.aretrieve(query)
        print(f"Retrieved {len(nodes)} nodes.")
        return RetrieverEvent(nodes=nodes)

    @step
    async def rerank(self, ctx: Context, ev: RetrieverEvent) -> RerankEvent:
        # Rerank the nodes
        ranker = LLMRerank(
            choice_batch_size=5, top_n=3, llm=Groq(model="llama-3.1-70b-versatile")
        )
        print(await ctx.get("query", default=None), flush=True)
        new_nodes = ranker.postprocess_nodes(
            ev.nodes, query_str=await ctx.get("query", default=None)
        )
        print(f"Reranked nodes to {len(new_nodes)}")
        print(new_nodes)
        return RerankEvent(nodes=new_nodes)

    @step
    async def synthesize(self, ctx: Context, ev: RerankEvent) -> StopEvent:
        """Return a streaming response using reranked nodes."""
        llm=Groq(model="llama-3.1-70b-versatile")
        summarizer = CompactAndRefine(llm=llm, streaming=True, verbose=True)
        query = await ctx.get("query", default=None)

        response = await summarizer.asynthesize(query, nodes=ev.nodes)
        return StopEvent(result=response)

from llama_index.utils.workflow import (
    draw_all_possible_flows,
    draw_most_recent_execution,
)

# # Draw all
draw_all_possible_flows(RAGWorkflow, filename="multi_step_workflow.html")

# Draw an execution
w = RAGWorkflow()

# Ingest the documents
index = await w.run(dirname="Data")
result = await w.run(query="What is Fibromyalgia?", index=index)
async for chunk in result.async_response_gen():
    print(chunk, end="", flush=True)
draw_most_recent_execution(w, filename="rag_flow_recent.html")

Balnk html is returned

import IPython
IPython.display.HTML(filename='/content/rag_flow_recent.html')

Content of HTML file

<html>
    <head>
        <meta charset="utf-8">
        
            <script src="lib/bindings/utils.js"></script>
            <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/vis-network/9.1.2/dist/dist/vis-network.min.css" integrity="sha512-WgxfT5LWjfszlPHXRmBWHkV2eceiWTOBvrKCNbdgDYTHrT2AeLCGbF4sZlZw3UMN3WtL0tGUoIAKsu8mllg/XA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
            <script src="https://cdnjs.cloudflare.com/ajax/libs/vis-network/9.1.2/dist/vis-network.min.js" integrity="sha512-LnvoEWDFrqGHlHmDD2101OrLcbsfkrzoSpvtSQtxK3RMnRV0eOkhhBN2dXHKRrUU8p2DGRTk35n4O8nWSVe1mQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
            
        
<center>
<h1></h1>
</center>

<!-- <link rel="stylesheet" href="../node_modules/vis/dist/vis.min.css" type="text/css" />
<script type="text/javascript" src="../node_modules/vis/dist/vis.js"> </script>-->
        <link
          href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
          rel="stylesheet"
          integrity="sha384-eOJMYsd53ii+scO/bJGFsiCZc+5NDVN2yr8+0RDqr0Ql0h+rP48ckxlpbzKgwra6"
          crossorigin="anonymous"
        />
        <script
          src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"
          integrity="sha384-JEW9xMcG8R+pH31jmWH6WWP0WintQrMb4s7ZOdauHnUtxwoG2vI5DkLtS3qm9Ekf"
          crossorigin="anonymous"
        ></script>


        <center>
          <h1></h1>
        </center>
        <style type="text/css">

             #mynetwork {
                 width: 100%;
                 height: 750px;
                 background-color: #ffffff;
                 border: 1px solid lightgray;
                 position: relative;
                 float: left;
             }

             

             

             
        </style>
    </head>


    <body>
        <div class="card" style="width: 100%">
            
            
            <div id="mynetwork" class="card-body"></div>
        </div>

        
        

        <script type="text/javascript">

              // initialize global variables.
              var edges;
              var nodes;
              var allNodes;
              var allEdges;
              var nodeColors;
              var originalNodes;
              var network;
              var container;
              var options, data;
              var filter = {
                  item : '',
                  property : '',
                  value : []
              };

              

              

              // This method is responsible for drawing the graph, returns the drawn network
              function drawGraph() {
                  var container = document.getElementById('mynetwork');

                  

                  // parsing and collecting nodes and edges from the python
                  nodes = new vis.DataSet([{"color": "#FFA07A", "id": "StopEvent", "label": "StopEvent", "shape": "ellipse"}, {"color": "#ADD8E6", "id": "_done", "label": "_done", "shape": "box"}, {"color": "#ADD8E6", "id": "ingest", "label": "ingest", "shape": "box"}, {"color": "#E27AFF", "id": "StartEvent", "label": "StartEvent", "shape": "ellipse"}, {"color": "#ADD8E6", "id": "rerank", "label": "rerank", "shape": "box"}, {"color": "#90EE90", "id": "RetrieverEvent", "label": "RetrieverEvent", "shape": "ellipse"}, {"color": "#ADD8E6", "id": "retrieve", "label": "retrieve", "shape": "box"}, {"color": "#ADD8E6", "id": "synthesize", "label": "synthesize", "shape": "box"}, {"color": "#90EE90", "id": "RerankEvent", "label": "RerankEvent", "shape": "ellipse"}]);
                  edges = new vis.DataSet([{"arrows": "to", "from": "StopEvent", "to": "_done"}, {"arrows": "to", "from": "StopEvent", "to": "_done"}, {"arrows": "to", "from": "ingest", "to": "StopEvent"}, {"arrows": "to", "from": "StartEvent", "to": "ingest"}, {"arrows": "to", "from": "rerank", "to": "RerankEvent"}, {"arrows": "to", "from": "RetrieverEvent", "to": "rerank"}, {"arrows": "to", "from": "retrieve", "to": "RetrieverEvent"}, {"arrows": "to", "from": "StartEvent", "to": "retrieve"}, {"arrows": "to", "from": "synthesize", "to": "StopEvent"}, {"arrows": "to", "from": "RerankEvent", "to": "synthesize"}]);

                  nodeColors = {};
                  allNodes = nodes.get({ returnType: "Object" });
                  for (nodeId in allNodes) {
                    nodeColors[nodeId] = allNodes[nodeId].color;
                  }
                  allEdges = edges.get({ returnType: "Object" });
                  // adding nodes and edges to the graph
                  data = {nodes: nodes, edges: edges};

                  var options = {
    "configure": {
        "enabled": false
    },
    "edges": {
        "color": {
            "inherit": true
        },
        "smooth": {
            "enabled": true,
            "type": "dynamic"
        }
    },
    "interaction": {
        "dragNodes": true,
        "hideEdgesOnDrag": false,
        "hideNodesOnDrag": false
    },
    "physics": {
        "enabled": true,
        "stabilization": {
            "enabled": true,
            "fit": true,
            "iterations": 1000,
            "onlyDynamicEdges": false,
            "updateInterval": 50
        }
    }
};

                  


                  

                  network = new vis.Network(container, data, options);

                  

                  

                  


                  

                  return network;

              }
              drawGraph();
        </script>
    </body>
</html>
Please help me with some directives to visualize the workflow

Version

llama_index.core==0.11.8

Steps to Reproduce

draw_all_possible_flows(RAGWorkflow, filename="multi_step_workflow.html")

Relevant Logs/Tracbacks

No response

@plaban1981 plaban1981 added bug Something isn't working triage Issue needs to be triaged/prioritized labels Sep 10, 2024
Copy link

dosubot bot commented Sep 10, 2024

To fix the issue of draw_all_possible_flows returning a blank HTML when trying to visualize the RAGWorkflow in the LlamaIndex library, ensure that the workflow has steps defined either in the class or the instance. The function relies on the presence of steps to generate the visualization. Here is the relevant part of the code that handles this:

# Add nodes from all steps
steps = get_steps_from_class(workflow)
if not steps:
    # If no steps are defined in the class, try to get them from the instance
    steps = get_steps_from_instance(workflow)

If steps is empty, the function will not be able to add any nodes or edges, resulting in a blank HTML. Make sure that your workflow object has steps defined. You can check this by ensuring that get_steps_from_class(workflow) or get_steps_from_instance(workflow) returns a non-empty dictionary.

Here is a minimal example to ensure your workflow has steps:

from llama_index.core.workflow import Workflow
from llama_index.core.decorators import step

class MyWorkflow(Workflow):
    @step
    def step1(self):
        pass

    @step
    def step2(self):
        pass

workflow = MyWorkflow()
draw_all_possible_flows(workflow)

Ensure that your workflow class or instance has steps decorated with @step so that they can be detected by get_steps_from_class or get_steps_from_instance.

Additionally, the draw_all_possible_flows function uses the pyvis library to create the visualization. Make sure you have pyvis installed and properly configured in your environment. The function also adds nodes and edges for stop events and steps, and it uses specific colors and shapes for different types of nodes:

from pyvis.network import Network

def draw_all_possible_flows(
    workflow: Workflow,
    filename: str = "workflow_all_flows.html",
    notebook: bool = False,
) -> None:
    net = Network(directed=True, height="750px", width="100%")

    # Add the nodes + edge for stop events
    net.add_node(
        StopEvent.__name__,
        label=StopEvent.__name__,
        color="#FFA07A",
        shape="ellipse",
    )
    net.add_node("_done", label="_done", color="#ADD8E6", shape="box")
    net.add_edge(StopEvent.__name__, "_done")

    # Add nodes from all steps
    steps = get_steps_from_class(workflow)
    if not steps:
        steps = get_steps_from_instance(workflow)

    for step_name, step_func in steps.items():
        step_config = getattr(step_func, "__step_config", None)
        if step_config is None:
            continue

        net.add_node(
            step_name, label=step_name, color="#ADD8E6", shape="box"
        )

        for event_type in step_config.accepted_events:
            net.add_node(
                event_type.__name__,
                label=event_type.__name__,
                color="#90EE90" if event_type != StartEvent else "#E27AFF",
                shape="ellipse",
            )

    # Add edges from all steps
    for step_name, step_func in steps.items():
        step_config = getattr(step_func, "__step_config", None)
        if step_config is None:
            continue

        for return_type in step_config.return_types:
            if return_type != type(None):
                net.add_edge(step_name, return_type.__name__)

        for event_type in step_config.accepted_events:
            net.add_edge(event_type.__name__, step_name)

    net.show(filename, notebook=notebook)

By ensuring that your workflow has steps defined and that pyvis is properly set up, you should be able to generate the HTML visualization without issues [1][2].

To continue talking to Dosu, mention @dosu.

@plaban1981
Copy link
Author

@dosu,

I have declared the steps in the workflow

class RAGWorkflow(Workflow):
    @step
    async def ingest(self, ctx: Context, ev: StartEvent) -> StopEvent | None:
        """Entry point to ingest a document, triggered by a StartEvent with `dirname`."""
        dirname = ev.get("dirname")
        if not dirname:
            return None

        documents = SimpleDirectoryReader(dirname).load_data()
        index = VectorStoreIndex.from_documents(
            documents=documents,
            embed_model=HuggingFaceEmbedding(model_name="BAAI/bge-small-en-v1.5"),
        )
        return StopEvent(result=index)

    @step
    async def retrieve(
        self, ctx: Context, ev: StartEvent
    ) -> RetrieverEvent | None:
        "Entry point for RAG, triggered by a StartEvent with `query`."
        query = ev.get("query")
        index = ev.get("index")

        if not query:
            return None

        print(f"Query the database with: {query}")

        # store the query in the global context
        await ctx.set("query", query)

        # get the index from the global context
        if index is None:
            print("Index is empty, load some documents before querying!")
            return None

        retriever = index.as_retriever(similarity_top_k=2)
        nodes = await retriever.aretrieve(query)
        print(f"Retrieved {len(nodes)} nodes.")
        return RetrieverEvent(nodes=nodes)

    @step
    async def rerank(self, ctx: Context, ev: RetrieverEvent) -> RerankEvent:
        # Rerank the nodes
        ranker = LLMRerank(
            choice_batch_size=5, top_n=3, llm=Groq(model="llama-3.1-70b-versatile")
        )
        print(await ctx.get("query", default=None), flush=True)
        new_nodes = ranker.postprocess_nodes(
            ev.nodes, query_str=await ctx.get("query", default=None)
        )
        print(f"Reranked nodes to {len(new_nodes)}")
        print(new_nodes)
        return RerankEvent(nodes=new_nodes)

    @step
    async def synthesize(self, ctx: Context, ev: RerankEvent) -> StopEvent:
        """Return a streaming response using reranked nodes."""
        llm=Groq(model="llama-3.1-70b-versatile")
        summarizer = CompactAndRefine(llm=llm, streaming=True, verbose=True)
        query = await ctx.get("query", default=None)

        response = await summarizer.asynthesize(query, nodes=ev.nodes)
        return StopEvent(result=response)

Also could you mention the package from which i can import get_steps_from_class

# Add nodes from all steps
from llama_index.core.workflow import get_steps_from_class, get_steps_from_instance
steps = get_steps_from_class(RAGWorkflow)
if not steps:
    # If no steps are defined in the class, try to get them from the instance
    steps = get_steps_from_instance(RAGWorkflow)
ImportError                               Traceback (most recent call last)
[<ipython-input-92-ebc12dff9b54>](https://localhost:8080/#) in <cell line: 2>()
      1 # Add nodes from all steps
----> 2 from llama_index.core.workflow import get_steps_from_class, get_steps_from_instance
      3 steps = get_steps_from_class(RAGWorkflow)
      4 if not steps:
      5     # If no steps are defined in the class, try to get them from the instance

ImportError: cannot import name 'get_steps_from_class' from 'llama_index.core.workflow' (/usr/local/lib/python3.10/dist-packages/llama_index/core/workflow/__init__.py)

---------------------------------------------------------------------------
NOTE: If your import is failing due to a missing package, you can
manually install dependencies using either !pip or !apt.

To view examples of installing some common dependencies, click the
"Open Examples" button below.

Copy link

dosubot bot commented Sep 10, 2024

To fix the ImportError for get_steps_from_class and get_steps_from_instance when trying to visualize the RAGWorkflow in the LlamaIndex library, you need to use the imports from llama_index.core.workflow.utils instead.

Here is the corrected code:

from llama_index.core.workflow.utils import get_steps_from_class, get_steps_from_instance
from llama_index.core.workflow.events import StartEvent, StopEvent
from llama_index.core.workflow.decorators import StepConfig
from llama_index.core.workflow.workflow import Workflow

def draw_all_possible_flows(
    workflow: Workflow,
    filename: str = "workflow_all_flows.html",
    notebook: bool = False,
) -> None:
    """Draws all possible flows of the workflow."""
    from pyvis.network import Network

    net = Network(directed=True, height="750px", width="100%")

    # Add the nodes + edge for stop events
    net.add_node(
        StopEvent.__name__,
        label=StopEvent.__name__,
        color="#FFA07A",
        shape="ellipse",
    )
    net.add_node("_done", label="_done", color="#ADD8E6", shape="box")
    net.add_edge(StopEvent.__name__, "_done")

    # Add nodes from all steps
    steps = get_steps_from_class(workflow)
    if not steps:
        # If no steps are defined in the class, try to get them from the instance
        steps = get_steps_from_instance(workflow)

    step_config: Optional[StepConfig] = None
    for step_name, step_func in steps.items():
        step_config = getattr(step_func, "__step_config", None)
        if step_config is None:
            continue

        net.add_node(
            step_name, label=step_name, color="#ADD8E6", shape="box"
        )  # Light blue for steps

        for event_type in step_config.accepted_events:
            net.add_node(
                event_type.__name__,
                label=event_type.__name__,
                color="#90EE90" if event_type != StartEvent else "#E27AFF",
                shape="ellipse",
            )  # Light green for events

    # Add edges from all steps
    for step_name, step_func in steps.items():
        step_config = getattr(step_func, "__step_config", None)

        if step_config is None:
            continue

        for return_type in step_config.return_types:
            if return_type != type(None):
                net.add_edge(step_name, return_type.__name__)

        for event_type in step_config.accepted_events:
            net.add_edge(event_type.__name__, step_name)

    net.show(filename, notebook=notebook)

Make sure to install the llama-index-utils-workflow package:

pip install llama-index-utils-workflow

This should resolve the ImportError and allow you to visualize the RAGWorkflow correctly [1][2][3][4].

To continue talking to Dosu, mention @dosu.

@plaban1981
Copy link
Author

@dosu

The response i get from RAGWORKFLOW is as follows. So I do not understand how the steps will not be defined.Please help me understand

multi_step_workflow.html
Query the database with: What is Fibromyalgia?
Retrieved 2 nodes.
What is Fibromyalgia?
Reranked nodes to 2
[NodeWithScore(node=TextNode(id_='2107de7b-2033-4094-83d0-64a471bf3232', embedding=None, metadata={'page_label': '137', 'file_name': 'fibromyalgia.pdf', 'file_path': '/content/Data/fibromyalgia.pdf', 'file_type': 'application/pdf', 'file_size': 632664, 'creation_date': '2024-09-10', 'last_modified_date': '2023-03-24'}, excluded_embed_metadata_keys=['file_name', 'file_type', 'file_size', 'creation_date', 'last_modified_date', 'last_accessed_date'], excluded_llm_metadata_keys=['file_name', 'file_type', 'file_size', 'creation_date', 'last_modified_date', 'last_accessed_date'], relationships={<NodeRelationship.SOURCE: '1'>: RelatedNodeInfo(node_id='f86fad28-72a5-4f88-a929-1712e9866659', node_type=<ObjectType.DOCUMENT: '4'>, metadata={'page_label': '137', 'file_name': 'fibromyalgia.pdf', 'file_path': '/content/Data/fibromyalgia.pdf', 'file_type': 'application/pdf', 'file_size': 632664, 'creation_date': '2024-09-10', 'last_modified_date': '2023-03-24'}, hash='c70178d6db587d29e77970e161c76a1abb2ef7d49978421819193e71322b44f4'), <NodeRelationship.NEXT: '3'>: RelatedNodeInfo(node_id='903818a8-0b83-41fe-88fa-cd39f67dbd52', node_type=<ObjectType.TEXT: '1'>, metadata={}, hash='ae86bbb0dc3fc0820ab5d3ea8545e68a9fe5ed83eec1bf43f822637cc1c887ed')}, text='February 2023  ◆ Volume 107, Number 2  [www.aafp.org/afp](http://www.aafp.org/afp)  American Family Physician  137Fibromyalgia is characterized by diffuse mus -\nculoskeletal pain, fatigue, poor sleep, and other \nsomatic symptoms.1 Chronic diffuse pain affects \n10% to 15% of adults in the general population worldwide, many of whom have fibromyalgia.\n2,3 \nApproximately 2% of people in the United States have fibromyalgia, although the prevalence var -\nies across populations and with the diagnostic criteria used.\n3 Fibromyalgia can occur in chil -\ndren and adults and is found worldwide and across cultures. Women are diagnosed more \nfrequently than men;   a Scot tish survey found \nthat women are diagnosed between two and 14 times as often as men depending on the crite -\nria used.\n3,4 Changes in the diagnostic criteria over the past decade, including the elimination of specific tender points, have resulted in more patients with chronic pain meeting the criteria for fibromyalgia.\n3-5\nPathophysiology\nFibromyalgia is likely caused by disordered cen -\ntral nociceptive signal processing that leads to sensitization expressed as hyperalgesia and allo -\ndynia, which is similar to chronic pain conditions such as irritable bowel syndrome, interstitial cys -\ntitis, chronic pelvic pain, and chronic low back pain.\n6,7 Functional brain imaging suggests that \nthis aberrant processing may be attributed to an imbalance between excitatory and inhibitory neu -\nrotransmitters, particularly within the insula.\n8 \nSuggested etiologies include dysfunction of the hypothalamic-pituitary-adrenal axis and the autonomic nervous system, diffuse inflammation, glial cell activation, small fiber neuropathy, and infections such as the Epstein-Barr virus, Lyme disease, and viral hepatitis.\n9 Twin studies suggest \na genetic component may also be a factor.10Fibromyalgia:   Diagn osis and Management\nBradford T. Winslow, MD, University of Colorado School of Medicine, Aurora, \nColorado;   Swedi sh Family Medicine Residency, Englewood, Colorado\nCarmen Vandal, MD, and Laurel Dang, MD, Swedish Family Medicine Residency, Englewood, Colorado\n CME  This clinical content conforms to AAFP criteria for \nCME. See CME Quiz on page 127.\nAuthor disclosure:   No relevant financial relationships.\nPatient information:   A handout on this topic, written by the \nauthors of this article, is available with the online version of \nthis article.Fibromyalgia is a chronic, centralized pain syndrome characterized by disordered processing of painful stimuli. Fibromyal -\ngia is diagnosed more frequently in women and occurs globally, affecting 2% of people in the United States. Patients with \nfibromyalgia have diffuse chronic pain, poor sleep, fatigue, cognitive dysfunc -\ntion, and mood disturbances. Comorbid conditions, such as functional somatic syndromes, psychiatric diagnoses, and rheumatologic conditions may be pres -\nent. The Fibromyalgia Rapid Screening Tool is a helpful screening method for patients with diffuse chronic pain. The American College of Rheumatology criteria or the Analgesic, Anesthetic, and Addiction Clinical Trial Translations Innovations Opportunities and Networks–American Pain Society Pain Taxonomy diagnostic criteria can diagnose fibromyalgia. Establishing the diagnosis and providing education can reassure patients and decrease unnecessary testing. A multidisciplinary approach that incorporates nonpharmacologic therapies and medications to address problematic symptoms is most effective. Patient educa -\ntion, exercise, and cognitive behavior therapy can improve pain and function. Duloxetine, milnacipran, pregabalin, and amitriptyline are potentially effective medications for fibromyalgia. Nonsteroi -\ndal anti-inflammatory drugs and opioids have not demonstrated benefits for fibromyalgia and have significant limitations.  \n(Am Fam Physician\n. 2023;  107(2):  137-1 44. Copyright © 2023 American Academy of Family Physicians.)\nIllustration by Jonathan Dimes\nDownloaded from the American Family Physician website at [www.aafp.org/afp](http://www.aafp.org/afp). Copyright © 2023  American Academy of Family Physicians. For the private, non -\ncommercial use of one individual user of the website. All other rights reserved. Contact [[email protected]](mailto:[email protected]) for copyright questions and/or permission requests.Downloaded from the American Family Physician website at [www.aafp.org/afp](http://www.aafp.org/afp). Copyright © 2023  American Academy of Family Physicians. For the private, non -\ncommercial use of one individual user of the website.', mimetype='text/plain', start_char_idx=0, end_char_idx=4474, text_template='{metadata_str}\n\n{content}', metadata_template='{key}: {value}', metadata_seperator='\n'), score=9.0), NodeWithScore(node=TextNode(id_='477ee79f-0a31-4140-b451-d80a664a6bb0', embedding=None, metadata={'page_label': '138', 'file_name': 'fibromyalgia.pdf', 'file_path': '/content/Data/fibromyalgia.pdf', 'file_type': 'application/pdf', 'file_size': 632664, 'creation_date': '2024-09-10', 'last_modified_date': '2023-03-24'}, excluded_embed_metadata_keys=['file_name', 'file_type', 'file_size', 'creation_date', 'last_modified_date', 'last_accessed_date'], excluded_llm_metadata_keys=['file_name', 'file_type', 'file_size', 'creation_date', 'last_modified_date', 'last_accessed_date'], relationships={<NodeRelationship.SOURCE: '1'>: RelatedNodeInfo(node_id='c6279116-f83a-4638-a63f-b57441c4fcec', node_type=<ObjectType.DOCUMENT: '4'>, metadata={'page_label': '138', 'file_name': 'fibromyalgia.pdf', 'file_path': '/content/Data/fibromyalgia.pdf', 'file_type': 'application/pdf', 'file_size': 632664, 'creation_date': '2024-09-10', 'last_modified_date': '2023-03-24'}, hash='f14fbdf6f0180dbf4c788fe4c81e58302ff38270f6ea6f8b9c961a071f568f13')}, text='138  American Family Physician  [www.aafp.org/afp](http://www.aafp.org/afp)  Volume 107, Number 2  ◆ February 2023\nFIBROMYALGIAClinical Presentation\nChronic diffuse pain is the predominant \nsymptom in most patients with fibromyalgia. Patients may also experience muscle stiffness and tenderness. The physical examination in patients with fibromyalgia generally finds diffuse tenderness without other unusual findings. If joint swelling, inflammation, or deformities are present, an alternative or additional diagnosis should be investigated.\n5 \nFatigue and sleep disturbances are also com -\nmon.5,11 Sleep disturbances include difficulty \nfalling and staying asleep, frequent awaken -\nings, or feeling unrefreshed after sleeping. Comorbid mental health diagnoses are com -\nmon, as are cognitive symptoms such as poor concentration, forgetfulness, or altered think -\ning.\n5,6,12 This cognitive dysfunction has been \ntermed “fibrofog” and is described by patients as a mental slowing that adversely affects daily activities.\n13\nThe presence of another painful disorder \ndoes not exclude the diagnosis of fibromyal -\ngia. The Fibromyalgia Rapid Screening Tool can screen patients with diffuse chronic pain to help distinguish between fibromyalgia and other conditions (Table 1) .\n14 The tool may SORT:   KEY RECOMMENDATIONS FOR PRACTICE\nClinical recommendationEvidence \nrating Comments\nThe diagnosis of fibromyalgia should be considered in patients \nwith diffuse pain, fatigue, and sleep disturbances that have been present for at least three months.\n5,11C Diagnosis of fibromyalgia can be made using AAPT 2019 diagnostic criteria or the American College of Radiology 2011/2016 criteria\nPatients with fibromyalgia should be offered a multidisci -\nplinary treatment approach that includes education, exercise, and nonpharmacologic and pharmacologic options.\n27,28C Consensus guidelines and systematic reviews\nCognitive behavior therapy leads to improvement in pain and disability in patients with fibromyalgia in the short and medium term.\n32,34,35A Systematic reviews demonstrate improvement\nAmitriptyline, cyclobenzaprine, duloxetine (Cymbalta), mil -\nnacipran (Savella), and pregabalin (Lyrica) are effective for pain in fibromyalgia.\n43,46-48,50,52,54A Systematic reviews demonstrate effectiveness of these medications\nAAPT = Analgesic, Anesthetic, and Addiction Clinical Trial Translations Innovations Opportunities and Networks–American Pain Society Pain \nTaxonomy.\nA = consistent, good-quality patient-oriented evidence;   B = inconsistent or limited-quality patient-oriented evidence;   C = consensus, disease -\noriented evidence, usual practice, expert opinion, or case series. For information about the SORT evidence rating system, go to https://  [www.aafp.\norg/afpsort.\nTABLE](http://www.aafp./norg/afpsort./nTABLE) 1\nFibromyalgia Rapid Screening Tool (FiRST)\n Yes\nI have pain all over my body.  \nMy pain is accompanied by a continuous and very unpleas -\nant general fatigue. \nMy pain feels like burns, electric shocks, or cramps.  \nMy pain is accompanied by other unusual sensations \nthroughout my body, such as pins and needles, tingling, or numbness. \nMy pain is accompanied by other health problems such as digestive problems, urinary problems, headaches, or restless legs. \nMy pain has a significant impact on my life, particularly on my sleep and my ability to concentrate, making me feel slower in general. \nTotal*  \n*—One point for each yes answer. A score of 5 or greater suggests fibromyalgia.\nAdapted with permission from Perrot S, Bouhassira D, Fermanian J;   CEDR (C ercle \nd’Etude de la Douleur en Rhumatologie). Development and validation of the Fibro -\nmyalgia Rapid Screening Tool (FiRST). Pain. 2010;  150(2):  255.\nDescargado para Boletin -BINASSS ([[email protected]](mailto:[email protected])) en National Library of Health and Social Security de ClinicalKey.es por Elsevier en marzo 24, \n2023. Para uso personal exclusivamente. No se permiten otros usos sin autorización. Copyright ©2023. Elsevier Inc. Todos los derechos reservados.', mimetype='text/plain', start_char_idx=0, end_char_idx=3975, text_template='{metadata_str}\n\n{content}', metadata_template='{key}: {value}', metadata_seperator='\n'), score=8.0)]
Fibromyalgia is a chronic, centralized pain syndrome characterized by disordered processing of painful stimuli. It is characterized by diffuse musculoskeletal pain, fatigue, poor sleep, and other somatic symptoms.rag_flow_recent.html

Copy link

dosubot bot commented Sep 10, 2024

To resolve the issue of draw_all_possible_flows returning a blank HTML when trying to visualize the RAGWorkflow in the LlamaIndex library, ensure that the workflow steps are correctly defined and that the __step_config attribute is properly set for each step. The function relies on this attribute to add nodes and edges to the visualization.

Here is the relevant part of the code that checks for __step_config:

steps = get_steps_from_class(workflow)
if not steps:
    steps = get_steps_from_instance(workflow)

step_config: Optional[StepConfig] = None
for step_name, step_func in steps.items():
    step_config = getattr(step_func, "__step_config", None)
    if step_config is None:
        continue

    net.add_node(
        step_name, label=step_name, color="#ADD8E6", shape="box"
    )  # Light blue for steps

    for event_type in step_config.accepted_events:
        net.add_node(
            event_type.__name__,
            label=event_type.__name__,
            color="#90EE90" if event_type != StartEvent else "#E27AFF",
            shape="ellipse",
        )  # Light green for events

# Add edges from all steps
for step_name, step_func in steps.items():
    step_config = getattr(step_func, "__step_config", None)

    if step_config is None:
        continue

    for return_type in step_config.return_types:
        if return_type != type(None):
            net.add_edge(step_name, return_type.__name__)

    for event_type in step_config.accepted_events:
        net.add_edge(event_type.__name__, step_name)

Make sure that each step function in your workflow has the __step_config attribute set correctly. If the steps are not properly configured, the function will not be able to add the necessary nodes and edges, resulting in a blank HTML.

If the steps are correctly defined and the issue persists, consider using the updated import as suggested by the deprecation warning:

from llama_index.utils.workflow import draw_all_possible_flows

This might resolve any underlying issues related to the deprecated function. Additionally, ensure that the pyvis library is correctly installed and imported, as it is used for rendering the network visualization [1][2].

To continue talking to Dosu, mention @dosu.

@plaban1981
Copy link
Author

@dosu
Can you please have a look at my workflow and advise what correction I need to make in order to have the steps configured.

from llama_index.core import SimpleDirectoryReader, VectorStoreIndex
from llama_index.core.response_synthesizers import CompactAndRefine
from llama_index.core.postprocessor.llm_rerank import LLMRerank
from llama_index.core.workflow import (
    Context,
    Workflow,
    StartEvent,
    StopEvent,
    step,
)

from llama_index.llms.openai import OpenAI
from llama_index.embeddings.openai import OpenAIEmbedding


class RAGWorkflow(Workflow):
    @step
    async def ingest(self, ctx: Context, ev: StartEvent) -> StopEvent | None:
        """Entry point to ingest a document, triggered by a StartEvent with `dirname`."""
        dirname = ev.get("dirname")
        if not dirname:
            return None

        documents = SimpleDirectoryReader(dirname).load_data()
        index = VectorStoreIndex.from_documents(
            documents=documents,
            embed_model=HuggingFaceEmbedding(model_name="BAAI/bge-small-en-v1.5"),
        )
        return StopEvent(result=index)

    @step
    async def retrieve(
        self, ctx: Context, ev: StartEvent
    ) -> RetrieverEvent | None:
        "Entry point for RAG, triggered by a StartEvent with `query`."
        query = ev.get("query")
        index = ev.get("index")

        if not query:
            return None

        print(f"Query the database with: {query}")

        # store the query in the global context
        await ctx.set("query", query)

        # get the index from the global context
        if index is None:
            print("Index is empty, load some documents before querying!")
            return None

        retriever = index.as_retriever(similarity_top_k=2)
        nodes = await retriever.aretrieve(query)
        print(f"Retrieved {len(nodes)} nodes.")
        return RetrieverEvent(nodes=nodes)

    @step
    async def rerank(self, ctx: Context, ev: RetrieverEvent) -> RerankEvent:
        # Rerank the nodes
        ranker = LLMRerank(
            choice_batch_size=5, top_n=3, llm=Groq(model="llama-3.1-70b-versatile")
        )
        print(await ctx.get("query", default=None), flush=True)
        new_nodes = ranker.postprocess_nodes(
            ev.nodes, query_str=await ctx.get("query", default=None)
        )
        print(f"Reranked nodes to {len(new_nodes)}")
        print(new_nodes)
        return RerankEvent(nodes=new_nodes)

    @step
    async def synthesize(self, ctx: Context, ev: RerankEvent) -> StopEvent:
        """Return a streaming response using reranked nodes."""
        llm=Groq(model="llama-3.1-70b-versatile")
        summarizer = CompactAndRefine(llm=llm, streaming=True, verbose=True)
        query = await ctx.get("query", default=None)

        response = await summarizer.asynthesize(query, nodes=ev.nodes)
        return StopEvent(result=response)

Copy link

dosubot bot commented Sep 10, 2024

Your RAGWorkflow class appears to be correctly defined with the necessary steps. However, there are a few things you should check to ensure the steps are properly configured and visualized:

  1. Ensure All Imports are Correct:
    Make sure you have all the necessary imports, including the RetrieverEvent and RerankEvent classes.

  2. Check the __step_config Attribute:
    Each step should have the __step_config attribute set by the @step decorator. This is usually handled automatically by the decorator, but you can verify it by checking the attributes of your step functions.

  3. Use Correct Imports for Visualization:
    Ensure you are using the correct imports for the visualization functions. The get_steps_from_class and get_steps_from_instance functions should be imported from llama_index.core.workflow.utils.

Here is your updated code with the necessary imports and a check for the __step_config attribute:

from llama_index.core import SimpleDirectoryReader, VectorStoreIndex
from llama_index.core.response_synthesizers import CompactAndRefine
from llama_index.core.postprocessor.llm_rerank import LLMRerank
from llama_index.core.workflow import (
    Context,
    Workflow,
    StartEvent,
    StopEvent,
    step,
)
from llama_index.core.workflow.utils import get_steps_from_class, get_steps_from_instance
from llama_index.llms.groq import Groq
from llama_index.embeddings.huggingface import HuggingFaceEmbedding

class RetrieverEvent(Event):
    """Result of running retrieval"""
    nodes: list[NodeWithScore]

class RerankEvent(Event):
    """Result of running reranking on retrieved nodes"""
    nodes: list[NodeWithScore]

class RAGWorkflow(Workflow):
    @step
    async def ingest(self, ctx: Context, ev: StartEvent) -> StopEvent | None:
        """Entry point to ingest a document, triggered by a StartEvent with `dirname`."""
        dirname = ev.get("dirname")
        if not dirname:
            return None

        documents = SimpleDirectoryReader(dirname).load_data()
        index = VectorStoreIndex.from_documents(
            documents=documents,
            embed_model=HuggingFaceEmbedding(model_name="BAAI/bge-small-en-v1.5"),
        )
        return StopEvent(result=index)

    @step
    async def retrieve(
        self, ctx: Context, ev: StartEvent
    ) -> RetrieverEvent | None:
        "Entry point for RAG, triggered by a StartEvent with `query`."
        query = ev.get("query")
        index = ev.get("index")

        if not query:
            return None

        print(f"Query the database with: {query}")

        # store the query in the global context
        await ctx.set("query", query)

        # get the index from the global context
        if index is None:
            print("Index is empty, load some documents before querying!")
            return None

        retriever = index.as_retriever(similarity_top_k=2)
        nodes = await retriever.aretrieve(query)
        print(f"Retrieved {len(nodes)} nodes.")
        return RetrieverEvent(nodes=nodes)

    @step
    async def rerank(self, ctx: Context, ev: RetrieverEvent) -> RerankEvent:
        # Rerank the nodes
        ranker = LLMRerank(
            choice_batch_size=5, top_n=3, llm=Groq(model="llama-3.1-70b-versatile")
        )
        print(await ctx.get("query", default=None), flush=True)
        new_nodes = ranker.postprocess_nodes(
            ev.nodes, query_str=await ctx.get("query", default=None)
        )
        print(f"Reranked nodes to {len(new_nodes)}")
        print(new_nodes)
        return RerankEvent(nodes=new_nodes)

    @step
    async def synthesize(self, ctx: Context, ev: RerankEvent) -> StopEvent:
        """Return a streaming response using reranked nodes."""
        llm = Groq(model="llama-3.1-70b-versatile")
        summarizer = CompactAndRefine(llm=llm, streaming=True, verbose=True)
        query = await ctx.get("query", default=None)

        response = await summarizer.asynthesize(query, nodes=ev.nodes)
        return StopEvent(result=response)

# Check if steps have __step_config attribute
workflow = RAGWorkflow()
steps = get_steps_from_class(RAGWorkflow)
if not steps:
    steps = get_steps_from_instance(workflow)

for step_name, step_func in steps.items():
    step_config = getattr(step_func, "__step_config", None)
    if step_config is None:
        print(f"Step {step_name} is missing __step_config")

# Visualization
from llama_index.utils.workflow import draw_all_possible_flows, draw_most_recent_execution

# Draw all possible flows
draw_all_possible_flows(RAGWorkflow, filename="multi_step_workflow.html")

# Draw the most recent execution
w = RAGWorkflow()

# Ingest the documents
index = await w.run(dirname="Data")
result = await w.run(query="What is Fibromyalgia?", index=index)
async for chunk in result.async_response_gen():
    print(chunk, end="", flush=True)
draw_most_recent_execution(w, filename="rag_flow_recent.html")

Ensure you have the pyvis library installed and properly configured. This should help you visualize the workflow steps correctly. If the steps are correctly defined and the __step_config attribute is set, the visualization should work as expected [1].

To continue talking to Dosu, mention @dosu.

@plaban1981
Copy link
Author

@dosu
I get the below response: i could see the steps config but still cannot visualize the workflow. I ma executing the code in google colab.
##Response:

steps class :{'_done': <function Workflow._done at 0x7ab01cf79750>, 'ingest': <function RAGWorkflow.ingest at 0x7ab004437d00>, 'rerank': <function RAGWorkflow.rerank at 0x7ab004f89ea0>, 'retrieve': <function RAGWorkflow.retrieve at 0x7ab004437be0>, 'synthesize': <function RAGWorkflow.synthesize at 0x7ab004f89750>}
step config :accepted_events=[<class 'llama_index.core.workflow.events.StopEvent'>] event_name='ev' return_types=[<class 'NoneType'>] context_parameter='ctx' num_workers=1 requested_services=[]
step config :accepted_events=[<class 'llama_index.core.workflow.events.StartEvent'>] event_name='ev' return_types=[<class 'llama_index.core.workflow.events.StopEvent'>] context_parameter='ctx' num_workers=1 requested_services=[]
step config :accepted_events=[<class '__main__.RetrieverEvent'>] event_name='ev' return_types=[<class '__main__.RerankEvent'>] context_parameter='ctx' num_workers=1 requested_services=[]
step config :accepted_events=[<class 'llama_index.core.workflow.events.StartEvent'>] event_name='ev' return_types=[<class '__main__.RetrieverEvent'>] context_parameter='ctx' num_workers=1 requested_services=[]
step config :accepted_events=[<class '__main__.RerankEvent'>] event_name='ev' return_types=[<class 'llama_index.core.workflow.events.StopEvent'>] context_parameter='ctx' num_workers=1 requested_services=[]
multi_step_workflow.html
Query the database with: What is the diagnosis of Fibromyalgia?
Retrieved 2 nodes.
What is the diagnosis of Fibromyalgia?
Reranked nodes to 2
[NodeWithScore(node=TextNode(id_='4e3abfef-75f0-488d-b8bc-a61b00d8f51c', embedding=None, metadata={'page_label': '138', 'file_name': 'fibromyalgia.pdf', 'file_path': '/content/Data/fibromyalgia.pdf', 'file_type': 'application/pdf', 'file_size': 632664, 'creation_date': '2024-09-10', 'last_modified_date': '2023-03-24'}, excluded_embed_metadata_keys=['file_name', 'file_type', 'file_size', 'creation_date', 'last_modified_date', 'last_accessed_date'], excluded_llm_metadata_keys=['file_name', 'file_type', 'file_size', 'creation_date', 'last_modified_date', 'last_accessed_date'], relationships={<NodeRelationship.SOURCE: '1'>: RelatedNodeInfo(node_id='227b8327-858a-4515-800f-edbe12b5185c', node_type=<ObjectType.DOCUMENT: '4'>, metadata={'page_label': '138', 'file_name': 'fibromyalgia.pdf', 'file_path': '/content/Data/fibromyalgia.pdf', 'file_type': 'application/pdf', 'file_size': 632664, 'creation_date': '2024-09-10', 'last_modified_date': '2023-03-24'}, hash='f14fbdf6f0180dbf4c788fe4c81e58302ff38270f6ea6f8b9c961a071f568f13')}, text='138  American Family Physician  [www.aafp.org/afp](http://www.aafp.org/afp)  Volume 107, Number 2  ◆ February 2023\nFIBROMYALGIAClinical Presentation\nChronic diffuse pain is the predominant \nsymptom in most patients with fibromyalgia. Patients may also experience muscle stiffness and tenderness. The physical examination in patients with fibromyalgia generally finds diffuse tenderness without other unusual findings. If joint swelling, inflammation, or deformities are present, an alternative or additional diagnosis should be investigated.\n5 \nFatigue and sleep disturbances are also com -\nmon.5,11 Sleep disturbances include difficulty \nfalling and staying asleep, frequent awaken -\nings, or feeling unrefreshed after sleeping. Comorbid mental health diagnoses are com -\nmon, as are cognitive symptoms such as poor concentration, forgetfulness, or altered think -\ning.\n5,6,12 This cognitive dysfunction has been \ntermed “fibrofog” and is described by patients as a mental slowing that adversely affects daily activities.\n13\nThe presence of another painful disorder \ndoes not exclude the diagnosis of fibromyal -\ngia. The Fibromyalgia Rapid Screening Tool can screen patients with diffuse chronic pain to help distinguish between fibromyalgia and other conditions (Table 1) .\n14 The tool may SORT:   KEY RECOMMENDATIONS FOR PRACTICE\nClinical recommendationEvidence \nrating Comments\nThe diagnosis of fibromyalgia should be considered in patients \nwith diffuse pain, fatigue, and sleep disturbances that have been present for at least three months.\n5,11C Diagnosis of fibromyalgia can be made using AAPT 2019 diagnostic criteria or the American College of Radiology 2011/2016 criteria\nPatients with fibromyalgia should be offered a multidisci -\nplinary treatment approach that includes education, exercise, and nonpharmacologic and pharmacologic options.\n27,28C Consensus guidelines and systematic reviews\nCognitive behavior therapy leads to improvement in pain and disability in patients with fibromyalgia in the short and medium term.\n32,34,35A Systematic reviews demonstrate improvement\nAmitriptyline, cyclobenzaprine, duloxetine (Cymbalta), mil -\nnacipran (Savella), and pregabalin (Lyrica) are effective for pain in fibromyalgia.\n43,46-48,50,52,54A Systematic reviews demonstrate effectiveness of these medications\nAAPT = Analgesic, Anesthetic, and Addiction Clinical Trial Translations Innovations Opportunities and Networks–American Pain Society Pain \nTaxonomy.\nA = consistent, good-quality patient-oriented evidence;   B = inconsistent or limited-quality patient-oriented evidence;   C = consensus, disease -\noriented evidence, usual practice, expert opinion, or case series. For information about the SORT evidence rating system, go to https://  [www.aafp.\norg/afpsort.\nTABLE](http://www.aafp./norg/afpsort./nTABLE) 1\nFibromyalgia Rapid Screening Tool (FiRST)\n Yes\nI have pain all over my body.  \nMy pain is accompanied by a continuous and very unpleas -\nant general fatigue. \nMy pain feels like burns, electric shocks, or cramps.  \nMy pain is accompanied by other unusual sensations \nthroughout my body, such as pins and needles, tingling, or numbness. \nMy pain is accompanied by other health problems such as digestive problems, urinary problems, headaches, or restless legs. \nMy pain has a significant impact on my life, particularly on my sleep and my ability to concentrate, making me feel slower in general. \nTotal*  \n*—One point for each yes answer. A score of 5 or greater suggests fibromyalgia.\nAdapted with permission from Perrot S, Bouhassira D, Fermanian J;   CEDR (C ercle \nd’Etude de la Douleur en Rhumatologie). Development and validation of the Fibro -\nmyalgia Rapid Screening Tool (FiRST). Pain. 2010;  150(2):  255.\nDescargado para Boletin -BINASSS ([[email protected]](mailto:[email protected])) en National Library of Health and Social Security de ClinicalKey.es por Elsevier en marzo 24, \n2023. Para uso personal exclusivamente. No se permiten otros usos sin autorización. Copyright ©2023. Elsevier Inc. Todos los derechos reservados.', mimetype='text/plain', start_char_idx=0, end_char_idx=3975, text_template='{metadata_str}\n\n{content}', metadata_template='{key}: {value}', metadata_seperator='\n'), score=9.0), NodeWithScore(node=TextNode(id_='083e93cc-64e7-44aa-85cc-8ae2ad47ae61', embedding=None, metadata={'page_label': '139', 'file_name': 'fibromyalgia.pdf', 'file_path': '/content/Data/fibromyalgia.pdf', 'file_type': 'application/pdf', 'file_size': 632664, 'creation_date': '2024-09-10', 'last_modified_date': '2023-03-24'}, excluded_embed_metadata_keys=['file_name', 'file_type', 'file_size', 'creation_date', 'last_modified_date', 'last_accessed_date'], excluded_llm_metadata_keys=['file_name', 'file_type', 'file_size', 'creation_date', 'last_modified_date', 'last_accessed_date'], relationships={<NodeRelationship.SOURCE: '1'>: RelatedNodeInfo(node_id='f72359da-bc09-45a8-86a4-0901f4ea1a95', node_type=<ObjectType.DOCUMENT: '4'>, metadata={'page_label': '139', 'file_name': 'fibromyalgia.pdf', 'file_path': '/content/Data/fibromyalgia.pdf', 'file_type': 'application/pdf', 'file_size': 632664, 'creation_date': '2024-09-10', 'last_modified_date': '2023-03-24'}, hash='9f71e1907c7d29dd4a7b1ca31d4b1afdf9d97f6c99e7e2ae16eb1371055778c6'), <NodeRelationship.PREVIOUS: '2'>: RelatedNodeInfo(node_id='fbace6ad-d8b5-4b26-a82e-448aed631cde', node_type=<ObjectType.TEXT: '1'>, metadata={'page_label': '139', 'file_name': 'fibromyalgia.pdf', 'file_path': '/content/Data/fibromyalgia.pdf', 'file_type': 'application/pdf', 'file_size': 632664, 'creation_date': '2024-09-10', 'last_modified_date': '2023-03-24'}, hash='c4c09343c5946d1df3284cc43f5b4019ac46fd509b2eb8ddb92b1c973bc36766')}, text='24\nDifferential Diagnosis\nThe differential diagnosis includes rheumatologic con -\nditions, such as rheumatoid arthritis, systemic lupus ery -\nthematosus, systemic sclerosis, spondyloarthropathies, polymyalgia rheumatica, and myopathies.\n5 Fibromyalgia \nshould be considered in patients with other rheumatologic diagnoses who do not respond well to treatment of their primary condition. Based on the history and examination \nTABLE 2\nAAPT 2019 Diagnostic Criteria \nfor Fibromyalgia\n1.  Multisite pain, defined as six or more pain sites from \na total of nine possible sites:  \nHead\nLeft armRight armChestAbdomenUpper back and spineLower spine, including buttocksLeft legRight leg\n2. Moderate to severe sleep problems or fatigue\n3. Symptoms present for at least three months\nAAPT = Analgesic, Anesthetic, and Addiction Clinical Trial Trans -\nlations Innovations Opportunities and Networks–American Pain \nSociety Pain Taxonomy.\nAdapted with permission from Arnold LM, Bennett RM, Crofford LJ, \net al. AAPT diagnostic criteria for fibromyalgia. J Pain. 2019;  20(6):  \n614-615.\nDescargado para Boletin -BINASSS ([[email protected]](mailto:[email protected])) en National Library of Health and Social Security de ClinicalKey.es por Elsevier en marzo 24, \n2023. Para uso personal exclusivamente. No se permiten otros usos sin autorización. Copyright ©2023. Elsevier Inc. Todos los derechos reservados.', mimetype='text/plain', start_char_idx=3593, end_char_idx=4966, text_template='{metadata_str}\n\n{content}', metadata_template='{key}: {value}', metadata_seperator='\n'), score=8.0)]
The diagnosis of fibromyalgia can be made using the AAPT 2019 diagnostic criteria or the American College of Radiology 2011/2016 criteria. According to the AAPT 2019 diagnostic criteria, the diagnosis of fibromyalgia requires the presence of the following three conditions: 

1. Multisite pain, defined as six or more pain sites from a total of nine possible sites, including the head, left arm, right arm, chest, abdomen, upper back and spine, lower spine, including buttocks, left leg, and right leg.

2. Moderate to severe sleep problems or fatigue.

3. Symptoms present for at least three months.

Additionally, the Fibromyalgia Rapid Screening Tool (FiRST) can be used to screen patients with diffuse chronic pain to help distinguish between fibromyalgia and other conditions.rag_flow_recent.html

[105]
0s
12

Please help me.

Copy link

dosubot bot commented Sep 10, 2024

Given that your workflow steps are correctly defined and the __step_config attribute is set, the issue might be related to the environment in which you are running the code (Google Colab) or the way the HTML file is being rendered.

Here are a few steps to troubleshoot and resolve the issue:

  1. Ensure pyvis is Installed:
    Make sure the pyvis library is installed in your Google Colab environment. You can install it using the following command:

    !pip install pyvis
  2. Check HTML File Content:
    After generating the HTML file, check its content to ensure that the nodes and edges are correctly populated. You can do this by opening the HTML file in a text editor or using the following code in Colab:

    with open("multi_step_workflow.html", "r") as file:
        content = file.read()
        print(content)
  3. Render HTML in Colab:
    Google Colab might not render the HTML file correctly using IPython.display.HTML. Instead, you can use the following approach to display the HTML content:

    from IPython.core.display import display, HTML
    
    with open("multi_step_workflow.html", "r") as file:
        display(HTML(file.read()))
  4. Use draw_all_possible_flows Correctly:
    Ensure you are using the draw_all_possible_flows function correctly. Here is a complete example:

    from llama_index.utils.workflow import draw_all_possible_flows, draw_most_recent_execution
    
    # Draw all possible flows
    draw_all_possible_flows(RAGWorkflow, filename="multi_step_workflow.html")
    
    # Draw the most recent execution
    w = RAGWorkflow()
    
    # Ingest the documents
    index = await w.run(dirname="Data")
    result = await w.run(query="What is Fibromyalgia?", index=index)
    async for chunk in result.async_response_gen():
        print(chunk, end="", flush=True)
    draw_most_recent_execution(w, filename="rag_flow_recent.html")
  5. Check for Errors in HTML Generation:
    Ensure there are no errors during the HTML generation process. If there are any errors, they should be printed in the Colab output.

  6. Verify Node and Edge Data:
    Verify that the nodes and edges are correctly populated in the drawGraph function within the HTML file. The nodes and edges should be defined as follows:

    nodes = new vis.DataSet([
        {"color": "#FFA07A", "id": "StopEvent", "label": "StopEvent", "shape": "ellipse"},
        {"color": "#ADD8E6", "id": "_done", "label": "_done", "shape": "box"},
        {"color": "#ADD8E6", "id": "ingest", "label": "ingest", "shape": "box"},
        {"color": "#E27AFF", "id": "StartEvent", "label": "StartEvent", "shape": "ellipse"},
        {"color": "#ADD8E6", "id": "rerank", "label": "rerank", "shape": "box"},
        {"color": "#90EE90", "id": "RetrieverEvent", "label": "RetrieverEvent", "shape": "ellipse"},
        {"color": "#ADD8E6", "id": "retrieve", "label": "retrieve", "shape": "box"},
        {"color": "#ADD8E6", "id": "synthesize", "label": "synthesize", "shape": "box"},
        {"color": "#90EE90", "id": "RerankEvent", "label": "RerankEvent", "shape": "ellipse"}
    ]);
    edges = new vis.DataSet([
        {"arrows": "to", "from": "StopEvent", "to": "_done"},
        {"arrows": "to", "from": "ingest", "to": "StopEvent"},
        {"arrows": "to", "from": "StartEvent", "to": "ingest"},
        {"arrows": "to", "from": "rerank", "to": "RerankEvent"},
        {"arrows": "to", "from": "RetrieverEvent", "to": "rerank"},
        {"arrows": "to", "from": "retrieve", "to": "RetrieverEvent"},
        {"arrows": "to", "from": "StartEvent", "to": "retrieve"},
        {"arrows": "to", "from": "synthesize", "to": "StopEvent"},
        {"arrows": "to", "from": "RerankEvent", "to": "synthesize"}
    ]);

By following these steps, you should be able to identify and resolve the issue with visualizing the workflow in Google Colab. If the problem persists, consider running the code in a local environment to see if the issue is specific to Colab.

To continue talking to Dosu, mention @dosu.

@plaban1981
Copy link
Author

@dosu

1. pyvis has been installed in colab

2. Check HTML File Content:

<html>
    <head>
        <meta charset="utf-8">
        
            <script src="lib/bindings/utils.js"></script>
            <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/vis-network/9.1.2/dist/dist/vis-network.min.css" integrity="sha512-WgxfT5LWjfszlPHXRmBWHkV2eceiWTOBvrKCNbdgDYTHrT2AeLCGbF4sZlZw3UMN3WtL0tGUoIAKsu8mllg/XA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
            <script src="https://cdnjs.cloudflare.com/ajax/libs/vis-network/9.1.2/dist/vis-network.min.js" integrity="sha512-LnvoEWDFrqGHlHmDD2101OrLcbsfkrzoSpvtSQtxK3RMnRV0eOkhhBN2dXHKRrUU8p2DGRTk35n4O8nWSVe1mQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
            
        
<center>
<h1></h1>
</center>

<!-- <link rel="stylesheet" href="../node_modules/vis/dist/vis.min.css" type="text/css" />
<script type="text/javascript" src="../node_modules/vis/dist/vis.js"> </script>-->
        <link
          href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
          rel="stylesheet"
          integrity="sha384-eOJMYsd53ii+scO/bJGFsiCZc+5NDVN2yr8+0RDqr0Ql0h+rP48ckxlpbzKgwra6"
          crossorigin="anonymous"
        />
        <script
          src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"
          integrity="sha384-JEW9xMcG8R+pH31jmWH6WWP0WintQrMb4s7ZOdauHnUtxwoG2vI5DkLtS3qm9Ekf"
          crossorigin="anonymous"
        ></script>


        <center>
          <h1></h1>
        </center>
        <style type="text/css">

             #mynetwork {
                 width: 100%;
                 height: 750px;
                 background-color: #ffffff;
                 border: 1px solid lightgray;
                 position: relative;
                 float: left;
             }

             

             

             
        </style>
    </head>


    <body>
        <div class="card" style="width: 100%">
            
            
            <div id="mynetwork" class="card-body"></div>
        </div>

        
        

        <script type="text/javascript">

              // initialize global variables.
              var edges;
              var nodes;
              var allNodes;
              var allEdges;
              var nodeColors;
              var originalNodes;
              var network;
              var container;
              var options, data;
              var filter = {
                  item : '',
                  property : '',
                  value : []
              };

              

              

              // This method is responsible for drawing the graph, returns the drawn network
              function drawGraph() {
                  var container = document.getElementById('mynetwork');

                  

                  // parsing and collecting nodes and edges from the python
                  nodes = new vis.DataSet([{"color": "#FFA07A", "id": "StopEvent", "label": "StopEvent", "shape": "ellipse"}, {"color": "#ADD8E6", "id": "_done", "label": "_done", "shape": "box"}, {"color": "#ADD8E6", "id": "ingest", "label": "ingest", "shape": "box"}, {"color": "#E27AFF", "id": "StartEvent", "label": "StartEvent", "shape": "ellipse"}, {"color": "#ADD8E6", "id": "rerank", "label": "rerank", "shape": "box"}, {"color": "#90EE90", "id": "RetrieverEvent", "label": "RetrieverEvent", "shape": "ellipse"}, {"color": "#ADD8E6", "id": "retrieve", "label": "retrieve", "shape": "box"}, {"color": "#ADD8E6", "id": "synthesize", "label": "synthesize", "shape": "box"}, {"color": "#90EE90", "id": "RerankEvent", "label": "RerankEvent", "shape": "ellipse"}]);
                  edges = new vis.DataSet([{"arrows": "to", "from": "StopEvent", "to": "_done"}, {"arrows": "to", "from": "StopEvent", "to": "_done"}, {"arrows": "to", "from": "ingest", "to": "StopEvent"}, {"arrows": "to", "from": "StartEvent", "to": "ingest"}, {"arrows": "to", "from": "rerank", "to": "RerankEvent"}, {"arrows": "to", "from": "RetrieverEvent", "to": "rerank"}, {"arrows": "to", "from": "retrieve", "to": "RetrieverEvent"}, {"arrows": "to", "from": "StartEvent", "to": "retrieve"}, {"arrows": "to", "from": "synthesize", "to": "StopEvent"}, {"arrows": "to", "from": "RerankEvent", "to": "synthesize"}]);

                  nodeColors = {};
                  allNodes = nodes.get({ returnType: "Object" });
                  for (nodeId in allNodes) {
                    nodeColors[nodeId] = allNodes[nodeId].color;
                  }
                  allEdges = edges.get({ returnType: "Object" });
                  // adding nodes and edges to the graph
                  data = {nodes: nodes, edges: edges};

                  var options = {
    "configure": {
        "enabled": false
    },
    "edges": {
        "color": {
            "inherit": true
        },
        "smooth": {
            "enabled": true,
            "type": "dynamic"
        }
    },
    "interaction": {
        "dragNodes": true,
        "hideEdgesOnDrag": false,
        "hideNodesOnDrag": false
    },
    "physics": {
        "enabled": true,
        "stabilization": {
            "enabled": true,
            "fit": true,
            "iterations": 1000,
            "onlyDynamicEdges": false,
            "updateInterval": 50
        }
    }
};

                  


                  

                  network = new vis.Network(container, data, options);

                  

                  

                  


                  

                  return network;

              }
              drawGraph();
        </script>
    </body>
</html>

3. Node and Edge Data:

// parsing and collecting nodes and edges from the python
nodes = new vis.DataSet([
{"color": "#FFA07A", "id": "StopEvent", "label": "StopEvent", "shape": "ellipse"}, 
{"color": "#ADD8E6", "id": "_done", "label": "_done", "shape": "box"}, 
{"color": "#ADD8E6", "id": "ingest", "label": "ingest", "shape": "box"}, 
{"color": "#E27AFF", "id": "StartEvent", "label": "StartEvent", "shape": "ellipse"}, 
{"color": "#ADD8E6", "id": "rerank", "label": "rerank", "shape": "box"}, 
{"color": "#90EE90", "id": "RetrieverEvent", "label": "RetrieverEvent", "shape": "ellipse"}, 
{"color": "#ADD8E6", "id": "retrieve", "label": "retrieve", "shape": "box"}, 
{"color": "#ADD8E6", "id": "synthesize", "label": "synthesize", "shape": "box"}, 
{"color": "#90EE90", "id": "RerankEvent", "label": "RerankEvent", "shape": "ellipse"}
]);


edges = new vis.DataSet([
{"arrows": "to", "from": "StopEvent", "to": "_done"}, 
{"arrows": "to", "from": "StopEvent", "to": "_done"}, 
{"arrows": "to", "from": "ingest", "to": "StopEvent"}, 
{"arrows": "to", "from": "StartEvent", "to": "ingest"}, 
{"arrows": "to", "from": "rerank", "to": "RerankEvent"}, 
{"arrows": "to", "from": "RetrieverEvent", "to": "rerank"}, 
{"arrows": "to", "from": "retrieve", "to": "RetrieverEvent"}, 
{"arrows": "to", "from": "StartEvent", "to": "retrieve"}, 
{"arrows": "to", "from": "synthesize", "to": "StopEvent"}, 
{"arrows": "to", "from": "RerankEvent", "to": "synthesize"}
]);

4. Render HTML in Colab: gives an empty display

Copy link

dosubot bot commented Sep 10, 2024

To fix the issue of draw_all_possible_flows returning a blank HTML when trying to visualize the RAGWorkflow in the LlamaIndex library, you should ensure that the notebook parameter is set to True when calling the net.show method if you are working within Google Colab. This parameter is crucial for rendering the visualization correctly in a notebook environment.

Here is the relevant part of the code with the notebook parameter set to True:

def draw_all_possible_flows(
    workflow: Workflow,
    filename: str = "workflow_all_flows.html",
    notebook: bool = True,  # Set notebook to True
) -> None:
    """Draws all possible flows of the workflow."""
    from pyvis.network import Network

    net = Network(directed=True, height="750px", width="100%")

    # Add the nodes + edge for stop events
    net.add_node(
        StopEvent.__name__,
        label=StopEvent.__name__,
        color="#FFA07A",
        shape="ellipse",
    )
    net.add_node("_done", label="_done", color="#ADD8E6", shape="box")
    net.add_edge(StopEvent.__name__, "_done")

    # Add nodes from all steps
    steps = get_steps_from_class(workflow)
    if not steps:
        # If no steps are defined in the class, try to get them from the instance
        steps = get_steps_from_instance(workflow)

    step_config: Optional[StepConfig] = None
    for step_name, step_func in steps.items():
        step_config = getattr(step_func, "__step_config", None)
        if step_config is None:
            continue

        net.add_node(
            step_name, label=step_name, color="#ADD8E6", shape="box"
        )  # Light blue for steps

        for event_type in step_config.accepted_events:
            net.add_node(
                event_type.__name__,
                label=event_type.__name__,
                color="#90EE90" if event_type != StartEvent else "#E27AFF",
                shape="ellipse",
            )  # Light green for events

    # Add edges from all steps
    for step_name, step_func in steps.items():
        step_config = getattr(step_func, "__step_config", None)

        if step_config is None:
            continue

        for return_type in step_config.return_types:
            if return_type != type(None):
                net.add_edge(step_name, return_type.__name__)

        for event_type in step_config.accepted_events:
            net.add_edge(event_type.__name__, step_name)

    net.show(filename, notebook=notebook)

By setting notebook=True, the visualization should render correctly within Google Colab [1].

To continue talking to Dosu, mention @dosu.

@plaban1981
Copy link
Author

@dosu
I tried by setting notebook=True, but encountered below error:

{'_done': <function Workflow._done at 0x7ab01cf79750>, 'ingest': <function RAGWorkflow.ingest at 0x7ab004437d00>, 'rerank': <function RAGWorkflow.rerank at 0x7ab004f89ea0>, 'retrieve': <function RAGWorkflow.retrieve at 0x7ab004437be0>, 'synthesize': <function RAGWorkflow.synthesize at 0x7ab004f89750>}
h.html
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
[<ipython-input-120-9933456b455a>](https://localhost:8080/#) in <cell line: 3>()
      1 # Draw an execution
      2 w = RAGWorkflow()
----> 3 draw_all_possible_flows(RAGWorkflow,filename="h.html")

3 frames
[<ipython-input-118-9d4175dc4c0e>](https://localhost:8080/#) in draw_all_possible_flows(workflow, filename, notebook)
     63             net.add_edge(event_type.__name__, step_name)
     64 
---> 65     net.show(filename, notebook=True)

[/usr/local/lib/python3.10/dist-packages/pyvis/network.py](https://localhost:8080/#) in show(self, name, local, notebook)
    544         print(name)
    545         if notebook:
--> 546             self.write_html(name, open_browser=False,notebook=True)
    547         else:
    548             self.write_html(name, open_browser=True)

[/usr/local/lib/python3.10/dist-packages/pyvis/network.py](https://localhost:8080/#) in write_html(self, name, local, notebook, open_browser)
    513         getcwd_name = name
    514         check_html(getcwd_name)
--> 515         self.html = self.generate_html(notebook=notebook)
    516 
    517         if self.cdn_resources == "local":

[/usr/local/lib/python3.10/dist-packages/pyvis/network.py](https://localhost:8080/#) in generate_html(self, name, local, notebook)
    477             physics_enabled = self.options.physics.enabled
    478 
--> 479         self.html = template.render(height=height,
    480                                     width=width,
    481                                     nodes=nodes,

AttributeError: 'NoneType' object has no attribute 'render'

Please advise

Copy link

dosubot bot commented Sep 10, 2024

To resolve the AttributeError: 'NoneType' object has no attribute 'render' when trying to visualize the RAGWorkflow using draw_all_possible_flows with notebook=True in Google Colab, you need to ensure that the pyvis library is properly configured to render within a Jupyter notebook environment.

Here is an example of how you can modify your code to work in Google Colab:

from pyvis.network import Network
from IPython.core.display import display, HTML

def draw_all_possible_flows(
    workflow: Workflow,
    filename: str = "workflow_all_flows.html",
    notebook: bool = True,  # Set notebook to True
) -> None:
    """Draws all possible flows of the workflow."""
    net = Network(directed=True, height="750px", width="100%")

    # Add the nodes + edge for stop events
    net.add_node(
        StopEvent.__name__,
        label=StopEvent.__name__,
        color="#FFA07A",
        shape="ellipse",
    )
    net.add_node("_done", label="_done", color="#ADD8E6", shape="box")
    net.add_edge(StopEvent.__name__, "_done")

    # Add nodes from all steps
    steps = get_steps_from_class(workflow)
    if not steps:
        # If no steps are defined in the class, try to get them from the instance
        steps = get_steps_from_instance(workflow)

    step_config: Optional[StepConfig] = None
    for step_name, step_func in steps.items():
        step_config = getattr(step_func, "__step_config", None)
        if step_config is None:
            continue

        net.add_node(
            step_name, label=step_name, color="#ADD8E6", shape="box"
        )  # Light blue for steps

        for event_type in step_config.accepted_events:
            net.add_node(
                event_type.__name__,
                label=event_type.__name__,
                color="#90EE90" if event_type != StartEvent else "#E27AFF",
                shape="ellipse",
            )  # Light green for events

    # Add edges from all steps
    for step_name, step_func in steps.items():
        step_config = getattr(step_func, "__step_config", None)

        if step_config is None:
            continue

        for return_type in step_config.return_types:
            if return_type != type(None):
                net.add_edge(step_name, return_type.__name__)

        for event_type in step_config.accepted_events:
            net.add_edge(event_type.__name__, step_name)

    if notebook:
        net.show(filename, notebook=True)
        with open(filename, "r") as file:
            display(HTML(file.read()))
    else:
        net.show(filename)

# Example usage in Google Colab
draw_all_possible_flows(
    RAGWorkflow, filename="multi_step_workflow.html", notebook=True
)

This code ensures that the visualization is properly rendered within the Google Colab notebook by using the notebook=True parameter in the net.show method and displaying the HTML content using IPython.display.HTML. Additionally, make sure to install the necessary dependencies as shown in the example:

!pip install pyvis llama-index-core llama-index-llms-openai llama-index-embeddings-openai llama-index-readers-file llama-index-utils-workflow

By following these steps, you should be able to visualize the RAGWorkflow correctly in Google Colab [1][2][3].

To continue talking to Dosu, mention @dosu.

@plaban1981
Copy link
Author

@dosu

did as instructructed but still encounter

AttributeError: 'NoneType' object has no attribute 'render'

Copy link

dosubot bot commented Sep 10, 2024

To resolve the AttributeError: 'NoneType' object has no attribute 'render' when trying to visualize the RAGWorkflow using draw_all_possible_flows with notebook=True in Google Colab, you need to ensure that the pyvis library is properly configured to render within a Jupyter notebook environment.

Here is an example of how you can modify your code to work in Google Colab:

from pyvis.network import Network
from IPython.core.display import display, HTML

def draw_all_possible_flows(
    workflow: Workflow,
    filename: str = "workflow_all_flows.html",
    notebook: bool = True,  # Set notebook to True
) -> None:
    """Draws all possible flows of the workflow."""
    net = Network(directed=True, height="750px", width="100%")

    # Add the nodes + edge for stop events
    net.add_node(
        StopEvent.__name__,
        label=StopEvent.__name__,
        color="#FFA07A",
        shape="ellipse",
    )
    net.add_node("_done", label="_done", color="#ADD8E6", shape="box")
    net.add_edge(StopEvent.__name__, "_done")

    # Add nodes from all steps
    steps = get_steps_from_class(workflow)
    if not steps:
        # If no steps are defined in the class, try to get them from the instance
        steps = get_steps_from_instance(workflow)

    step_config: Optional[StepConfig] = None
    for step_name, step_func in steps.items():
        step_config = getattr(step_func, "__step_config", None)
        if step_config is None:
            continue

        net.add_node(
            step_name, label=step_name, color="#ADD8E6", shape="box"
        )  # Light blue for steps

        for event_type in step_config.accepted_events:
            net.add_node(
                event_type.__name__,
                label=event_type.__name__,
                color="#90EE90" if event_type != StartEvent else "#E27AFF",
                shape="ellipse",
            )  # Light green for events

    # Add edges from all steps
    for step_name, step_func in steps.items():
        step_config = getattr(step_func, "__step_config", None)

        if step_config is None:
            continue

        for return_type in step_config.return_types:
            if return_type != type(None):
                net.add_edge(step_name, return_type.__name__)

        for event_type in step_config.accepted_events:
            net.add_edge(event_type.__name__, step_name)

    if notebook:
        net.show(filename, notebook=True)
        with open(filename, "r") as file:
            display(HTML(file.read()))
    else:
        net.show(filename)

# Example usage in Google Colab
draw_all_possible_flows(
    RAGWorkflow, filename="multi_step_workflow.html", notebook=True
)

This code ensures that the visualization is properly rendered within the Google Colab notebook by using the notebook=True parameter in the net.show method and displaying the HTML content using IPython.display.HTML. Additionally, make sure to install the necessary dependencies as shown in the example:

!pip install pyvis llama-index-core llama-index-llms-openai llama-index-embeddings-openai llama-index-readers-file llama-index-utils-workflow

By following these steps, you should be able to visualize the RAGWorkflow correctly in Google Colab [1][2][3].

To continue talking to Dosu, mention @dosu.

@plaban1981
Copy link
Author

@dosu

I am now trying it in my local environment and I have python3.9.12. I encounter below error : How to resolve

TypeError: unsupported operand type(s) for |: 'ModelMetaclass' and 'NoneType



---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[5], [line 25](vscode-notebook-cell:?execution_count=5&line=25)
     [22](vscode-notebook-cell:?execution_count=5&line=22)     """Result of running reranking on retrieved nodes"""
     [23](vscode-notebook-cell:?execution_count=5&line=23)     nodes: list[NodeWithScore]
---> [25](vscode-notebook-cell:?execution_count=5&line=25) class RAGWorkflow(Workflow):
     [26](vscode-notebook-cell:?execution_count=5&line=26)     @step
     [27](vscode-notebook-cell:?execution_count=5&line=27)     async def ingest(self, ctx: Context, ev: StartEvent) -> StopEvent | None:
     [28](vscode-notebook-cell:?execution_count=5&line=28)         """Entry point to ingest a document, triggered by a StartEvent with `dirname`."""

Cell In[5], [line 27](vscode-notebook-cell:?execution_count=5&line=27)
     [25](vscode-notebook-cell:?execution_count=5&line=25) class RAGWorkflow(Workflow):
     [26](vscode-notebook-cell:?execution_count=5&line=26)     @step
---> [27](vscode-notebook-cell:?execution_count=5&line=27)     async def ingest(self, ctx: Context, ev: StartEvent) -> StopEvent | None:
     [28](vscode-notebook-cell:?execution_count=5&line=28)         """Entry point to ingest a document, triggered by a StartEvent with `dirname`."""
     [29](vscode-notebook-cell:?execution_count=5&line=29)         dirname = ev.get("dirname")

File c:\Users\PLNAYAK\Documents\workflow\workf\lib\site-packages\llama_index\core\workflow\decorators.py:73, in step(workflow, pass_context, num_workers, *args)
     [70](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:70) if len(args):
     [71](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:71)     # The decorator was used without parentheses, like `@step`
     [72](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:72)     func = args[0]
---> [73](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:73)     decorator(func)
     [74](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:74)     return func
     [75](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:75) return decorator

File c:\Users\PLNAYAK\Documents\workflow\workf\lib\site-packages\llama_index\core\workflow\decorators.py:47, in step.<locals>.decorator(func)
     [42](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:42)     raise WorkflowValidationError(
     [43](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:43)         "num_workers must be an integer greater than 0"
     [44](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:44)     )
     [46](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:46) # This will raise providing a message with the specific validation failure
---> [47](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:47) spec = inspect_signature(func)
     [48](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:48) validate_step_signature(spec)
     [49](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:49) event_name, accepted_events = next(iter(spec.accepted_events.items()))

File c:\Users\PLNAYAK\Documents\workflow\workf\lib\site-packages\llama_index\core\workflow\utils.py:86, in inspect_signature(fn)
     [76](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:76)         default_value = None
     [78](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:78)     requested_services.append(
     [79](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:79)         ServiceDefinition(
     [80](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:80)             name=name, service=param_types[0], default_value=default_value
     [81](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:81)         )
     [82](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:82)     )
     [84](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:84) return StepSignatureSpec(
     [85](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:85)     accepted_events=accepted_events,
---> [86](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:86)     return_types=_get_return_types(fn),
     [87](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:87)     context_parameter=context_parameter,
     [88](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:88)     requested_services=requested_services,
     [89](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:89) )

File c:\Users\PLNAYAK\Documents\workflow\workf\lib\site-packages\llama_index\core\workflow\utils.py:145, in _get_return_types(func)
    [140](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:140) def _get_return_types(func: Callable) -> List[Any]:
    [141](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:141)     """Extract the return type hints from a function.
    [142](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:142) 
    [143](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:143)     Handles Union, Optional, and List types.
    [144](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:144)     """
--> [145](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:145)     type_hints = get_type_hints(func)
    [146](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:146)     return_hint = type_hints.get("return")
    [147](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:147)     if return_hint is None:

File C:\Program Files\Python39\lib\typing.py:1449, in get_type_hints(obj, globalns, localns, include_extras)
   [1447](file:///C:/Program%20Files/Python39/lib/typing.py:1447) if isinstance(value, str):
   [1448](file:///C:/Program%20Files/Python39/lib/typing.py:1448)     value = ForwardRef(value)
-> [1449](file:///C:/Program%20Files/Python39/lib/typing.py:1449) value = _eval_type(value, globalns, localns)
   [1450](file:///C:/Program%20Files/Python39/lib/typing.py:1450) if name in defaults and defaults[name] is None:
   [1451](file:///C:/Program%20Files/Python39/lib/typing.py:1451)     value = Optional[value]

File C:\Program Files\Python39\lib\typing.py:283, in _eval_type(t, globalns, localns, recursive_guard)
    [277](file:///C:/Program%20Files/Python39/lib/typing.py:277) """Evaluate all forward references in the given type t.
    [278](file:///C:/Program%20Files/Python39/lib/typing.py:278) For use of globalns and localns see the docstring for get_type_hints().
    [279](file:///C:/Program%20Files/Python39/lib/typing.py:279) recursive_guard is used to prevent prevent infinite recursion
    [280](file:///C:/Program%20Files/Python39/lib/typing.py:280) with recursive ForwardRef.
    [281](file:///C:/Program%20Files/Python39/lib/typing.py:281) """
    [282](file:///C:/Program%20Files/Python39/lib/typing.py:282) if isinstance(t, ForwardRef):
--> [283](file:///C:/Program%20Files/Python39/lib/typing.py:283)     return t._evaluate(globalns, localns, recursive_guard)
    [284](file:///C:/Program%20Files/Python39/lib/typing.py:284) if isinstance(t, (_GenericAlias, GenericAlias)):
    [285](file:///C:/Program%20Files/Python39/lib/typing.py:285)     ev_args = tuple(_eval_type(a, globalns, localns, recursive_guard) for a in t.__args__)

File C:\Program Files\Python39\lib\typing.py:539, in ForwardRef._evaluate(self, globalns, localns, recursive_guard)
    [536](file:///C:/Program%20Files/Python39/lib/typing.py:536) elif localns is None:
    [537](file:///C:/Program%20Files/Python39/lib/typing.py:537)     localns = globalns
    [538](file:///C:/Program%20Files/Python39/lib/typing.py:538) type_ =_type_check(
--> [539](file:///C:/Program%20Files/Python39/lib/typing.py:539)     eval(self.__forward_code__, globalns, localns),
    [540](file:///C:/Program%20Files/Python39/lib/typing.py:540)     "Forward references must evaluate to types.",
    [541](file:///C:/Program%20Files/Python39/lib/typing.py:541)     is_argument=self.__forward_is_argument__,
    [542](file:///C:/Program%20Files/Python39/lib/typing.py:542) )
    [543](file:///C:/Program%20Files/Python39/lib/typing.py:543) self.__forward_value__ = _eval_type(
    [544](file:///C:/Program%20Files/Python39/lib/typing.py:544)     type_, globalns, localns, recursive_guard | {self.__forward_arg__}
    [545](file:///C:/Program%20Files/Python39/lib/typing.py:545) )
    [546](file:///C:/Program%20Files/Python39/lib/typing.py:546) self.__forward_evaluated__ = True

File <string>:1

TypeError: unsupported operand type(s) for |: 'ModelMetaclass' and 'NoneType'

Copy link

dosubot bot commented Sep 10, 2024

The error TypeError: unsupported operand type(s) for |: 'ModelMetaclass' and 'NoneType' is likely caused by the use of the | operator for type annotations, which is supported in Python 3.10 and later. Since you are using Python 3.9.12, you should use the Union type from the typing module instead.

Here is the corrected code for the RAGWorkflow class:

from typing import Union
from llama_index.core import SimpleDirectoryReader, VectorStoreIndex
from llama_index.core.response_synthesizers import CompactAndRefine
from llama_index.core.postprocessor.llm_rerank import LLMRerank
from llama_index.core.workflow import (
    Context,
    Workflow,
    StartEvent,
    StopEvent,
    step,
)
from llama_index.llms.groq import Groq
from llama_index.embeddings.huggingface import HuggingFaceEmbedding

class RetrieverEvent(Event):
    """Result of running retrieval"""
    nodes: list[NodeWithScore]

class RerankEvent(Event):
    """Result of running reranking on retrieved nodes"""
    nodes: list[NodeWithScore]

class RAGWorkflow(Workflow):
    @step
    async def ingest(self, ctx: Context, ev: StartEvent) -> Union[StopEvent, None]:
        """Entry point to ingest a document, triggered by a StartEvent with `dirname`."""
        dirname = ev.get("dirname")
        if not dirname:
            return None

        documents = SimpleDirectoryReader(dirname).load_data()
        index = VectorStoreIndex.from_documents(
            documents=documents,
            embed_model=HuggingFaceEmbedding(model_name="BAAI/bge-small-en-v1.5"),
        )
        return StopEvent(result=index)

    @step
    async def retrieve(
        self, ctx: Context, ev: StartEvent
    ) -> Union[RetrieverEvent, None]:
        "Entry point for RAG, triggered by a StartEvent with `query`."
        query = ev.get("query")
        index = ev.get("index")

        if not query:
            return None

        print(f"Query the database with: {query}")

        # store the query in the global context
        await ctx.set("query", query)

        # get the index from the global context
        if index is None:
            print("Index is empty, load some documents before querying!")
            return None

        retriever = index.as_retriever(similarity_top_k=2)
        nodes = await retriever.aretrieve(query)
        print(f"Retrieved {len(nodes)} nodes.")
        return RetrieverEvent(nodes=nodes)

    @step
    async def rerank(self, ctx: Context, ev: RetrieverEvent) -> RerankEvent:
        # Rerank the nodes
        ranker = LLMRerank(
            choice_batch_size=5, top_n=3, llm=Groq(model="llama-3.1-70b-versatile")
        )
        print(await ctx.get("query", default=None), flush=True)
        new_nodes = ranker.postprocess_nodes(
            ev.nodes, query_str=await ctx.get("query", default=None)
        )
        print(f"Reranked nodes to {len(new_nodes)}")
        return RerankEvent(nodes=new_nodes)

    @step
    async def synthesize(self, ctx: Context, ev: RerankEvent) -> StopEvent:
        """Return a streaming response using reranked nodes."""
        llm = Groq(model="llama-3.1-70b-versatile")
        summarizer = CompactAndRefine(llm=llm, streaming=True, verbose=True)
        query = await ctx.get("query", default=None)

        response = await summarizer.asynthesize(query, nodes=ev.nodes)
        return StopEvent(result=response)

By replacing the | operator with Union from the typing module, the code should now be compatible with Python 3.9.12 [1].

To continue talking to Dosu, mention @dosu.

@plaban1981
Copy link
Author

@dosu

I now encounter the below eror:

---------------------------------------------------------------------------
WorkflowValidationError                   Traceback (most recent call last)
Cell In[7], [line 25](vscode-notebook-cell:?execution_count=7&line=25)
     [22](vscode-notebook-cell:?execution_count=7&line=22)     """Result of running reranking on retrieved nodes"""
     [23](vscode-notebook-cell:?execution_count=7&line=23)     nodes: list[NodeWithScore]
---> [25](vscode-notebook-cell:?execution_count=7&line=25) class RAGWorkflow(Workflow):
     [26](vscode-notebook-cell:?execution_count=7&line=26)     @step
     [27](vscode-notebook-cell:?execution_count=7&line=27)     async def ingest(self, ctx: Context, ev: StartEvent) -> Union[StopEvent, None]:
     [28](vscode-notebook-cell:?execution_count=7&line=28)         """Entry point to ingest a document, triggered by a StartEvent with `dirname`."""

Cell In[7], [line 27](vscode-notebook-cell:?execution_count=7&line=27)
     [25](vscode-notebook-cell:?execution_count=7&line=25) class RAGWorkflow(Workflow):
     [26](vscode-notebook-cell:?execution_count=7&line=26)     @step
---> [27](vscode-notebook-cell:?execution_count=7&line=27)     async def ingest(self, ctx: Context, ev: StartEvent) -> Union[StopEvent, None]:
     [28](vscode-notebook-cell:?execution_count=7&line=28)         """Entry point to ingest a document, triggered by a StartEvent with `dirname`."""
     [29](vscode-notebook-cell:?execution_count=7&line=29)         dirname = ev.get("dirname")

File c:\Users\PLNAYAK\Documents\workflow\workf\lib\site-packages\llama_index\core\workflow\decorators.py:73, in step(workflow, pass_context, num_workers, *args)
     [70](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:70) if len(args):
     [71](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:71)     # The decorator was used without parentheses, like `@step`
     [72](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:72)     func = args[0]
---> [73](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:73)     decorator(func)
     [74](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:74)     return func
     [75](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:75) return decorator

File c:\Users\PLNAYAK\Documents\workflow\workf\lib\site-packages\llama_index\core\workflow\decorators.py:48, in step.<locals>.decorator(func)
     [46](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:46) # This will raise providing a message with the specific validation failure
     [47](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:47) spec = inspect_signature(func)
---> [48](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:48) validate_step_signature(spec)
     [49](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:49) event_name, accepted_events = next(iter(spec.accepted_events.items()))
     [51](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:51) # store the configuration in the function object

File c:\Users\PLNAYAK\Documents\workflow\workf\lib\site-packages\llama_index\core\workflow\utils.py:96, in validate_step_signature(spec)
     [94](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:94) if num_of_events == 0:
     [95](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:95)     msg = "Step signature must have at least one parameter annotated as type Event"
---> [96](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:96)     raise WorkflowValidationError(msg)
     [97](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:97) elif num_of_events > 1:
     [98](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:98)     msg = f"Step signature must contain exactly one parameter of type Event but found {num_of_events}."

WorkflowValidationError: Step signature must have at least one parameter annotated as type Event

Copy link

dosubot bot commented Sep 10, 2024

The WorkflowValidationError: Step signature must have at least one parameter annotated as type Event error indicates that the step functions in your RAGWorkflow class need to have at least one parameter annotated with the Event type.

Here is the corrected code for your RAGWorkflow class:

  1. Ensure that the RetrieverEvent and RerankEvent classes are correctly defined and inherit from Event.
  2. Ensure that the step functions have parameters annotated with the Event type.

Here is the complete code:

from typing import Union, List
from llama_index.core import SimpleDirectoryReader, VectorStoreIndex
from llama_index.core.response_synthesizers import CompactAndRefine
from llama_index.core.postprocessor.llm_rerank import LLMRerank
from llama_index.core.workflow import (
    Context,
    Workflow,
    StartEvent,
    StopEvent,
    step,
    Event,
)
from llama_index.core.schema import NodeWithScore, QueryType
from llama_index.llms.groq import Groq
from llama_index.embeddings.huggingface import HuggingFaceEmbedding

class RetrieverEvent(Event):
    """Result of running retrieval"""
    str_or_query_bundle: QueryType
    nodes: List[NodeWithScore]

    @classmethod
    def class_name(cls) -> str:
        """Class name."""
        return "RetrieverEvent"

class RerankEvent(Event):
    """Result of running reranking on retrieved nodes"""
    str_or_query_bundle: QueryType
    nodes: List[NodeWithScore]

    @classmethod
    def class_name(cls) -> str:
        """Class name."""
        return "RerankEvent"

class RAGWorkflow(Workflow):
    @step
    async def ingest(self, ctx: Context, ev: StartEvent) -> Union[StopEvent, None]:
        """Entry point to ingest a document, triggered by a StartEvent with `dirname`."""
        dirname = ev.get("dirname")
        if not dirname:
            return None

        documents = SimpleDirectoryReader(dirname).load_data()
        index = VectorStoreIndex.from_documents(
            documents=documents,
            embed_model=HuggingFaceEmbedding(model_name="BAAI/bge-small-en-v1.5"),
        )
        return StopEvent(result=index)

    @step
    async def retrieve(
        self, ctx: Context, ev: StartEvent
    ) -> Union[RetrieverEvent, None]:
        "Entry point for RAG, triggered by a StartEvent with `query`."
        query = ev.get("query")
        index = ev.get("index")

        if not query:
            return None

        print(f"Query the database with: {query}")

        # store the query in the global context
        await ctx.set("query", query)

        # get the index from the global context
        if index is None:
            print("Index is empty, load some documents before querying!")
            return None

        retriever = index.as_retriever(similarity_top_k=2)
        nodes = await retriever.aretrieve(query)
        print(f"Retrieved {len(nodes)} nodes.")
        return RetrieverEvent(nodes=nodes)

    @step
    async def rerank(self, ctx: Context, ev: RetrieverEvent) -> RerankEvent:
        # Rerank the nodes
        ranker = LLMRerank(
            choice_batch_size=5, top_n=3, llm=Groq(model="llama-3.1-70b-versatile")
        )
        print(await ctx.get("query", default=None), flush=True)
        new_nodes = ranker.postprocess_nodes(
            ev.nodes, query_str=await ctx.get("query", default=None)
        )
        print(f"Reranked nodes to {len(new_nodes)}")
        return RerankEvent(nodes=new_nodes)

    @step
    async def synthesize(self, ctx: Context, ev: RerankEvent) -> StopEvent:
        """Return a streaming response using reranked nodes."""
        llm = Groq(model="llama-3.1-70b-versatile")
        summarizer = CompactAndRefine(llm=llm, streaming=True, verbose=True)
        query = await ctx.get("query", default=None)

        response = await summarizer.asynthesize(query, nodes=ev.nodes)
        return StopEvent(result=response)

This should resolve the WorkflowValidationError by ensuring that each step function has a parameter annotated with the Event type [1].

To continue talking to Dosu, mention @dosu.

@plaban1981
Copy link
Author

@dosu
Encounter below error:

---------------------------------------------------------------------------
WorkflowValidationError                   Traceback (most recent call last)
Cell In[9], [line 41](vscode-notebook-cell:?execution_count=9&line=41)
     [38](vscode-notebook-cell:?execution_count=9&line=38)         """Class name."""
     [39](vscode-notebook-cell:?execution_count=9&line=39)         return "RerankEvent"
---> [41](vscode-notebook-cell:?execution_count=9&line=41) class RAGWorkflow(Workflow):
     [42](vscode-notebook-cell:?execution_count=9&line=42)     @step
     [43](vscode-notebook-cell:?execution_count=9&line=43)     async def ingest(self, ctx: Context, ev: StartEvent) -> Union[StopEvent, None]:
     [44](vscode-notebook-cell:?execution_count=9&line=44)         """Entry point to ingest a document, triggered by a StartEvent with `dirname`."""

Cell In[9], [line 43](vscode-notebook-cell:?execution_count=9&line=43)
     [41](vscode-notebook-cell:?execution_count=9&line=41) class RAGWorkflow(Workflow):
     [42](vscode-notebook-cell:?execution_count=9&line=42)     @step
---> [43](vscode-notebook-cell:?execution_count=9&line=43)     async def ingest(self, ctx: Context, ev: StartEvent) -> Union[StopEvent, None]:
     [44](vscode-notebook-cell:?execution_count=9&line=44)         """Entry point to ingest a document, triggered by a StartEvent with `dirname`."""
     [45](vscode-notebook-cell:?execution_count=9&line=45)         dirname = ev.get("dirname")

File c:\Users\PLNAYAK\Documents\workflow\workf\lib\site-packages\llama_index\core\workflow\decorators.py:73, in step(workflow, pass_context, num_workers, *args)
     [70](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:70) if len(args):
     [71](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:71)     # The decorator was used without parentheses, like `@step`
     [72](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:72)     func = args[0]
---> [73](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:73)     decorator(func)
     [74](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:74)     return func
     [75](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:75) return decorator

File c:\Users\PLNAYAK\Documents\workflow\workf\lib\site-packages\llama_index\core\workflow\decorators.py:48, in step.<locals>.decorator(func)
     [46](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:46) # This will raise providing a message with the specific validation failure
     [47](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:47) spec = inspect_signature(func)
---> [48](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:48) validate_step_signature(spec)
     [49](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:49) event_name, accepted_events = next(iter(spec.accepted_events.items()))
     [51](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:51) # store the configuration in the function object

File c:\Users\PLNAYAK\Documents\workflow\workf\lib\site-packages\llama_index\core\workflow\utils.py:96, in validate_step_signature(spec)
     [94](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:94) if num_of_events == 0:
     [95](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:95)     msg = "Step signature must have at least one parameter annotated as type Event"
---> [96](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:96)     raise WorkflowValidationError(msg)
     [97](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:97) elif num_of_events > 1:
     [98](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:98)     msg = f"Step signature must contain exactly one parameter of type Event but found {num_of_events}."****

Copy link

dosubot bot commented Sep 10, 2024

The WorkflowValidationError: Step signature must have at least one parameter annotated as type Event error indicates that the step functions in your RAGWorkflow class need to have at least one parameter annotated with the Event type.

Here is the corrected code for your RAGWorkflow class:

  1. Ensure that the RetrieverEvent and RerankEvent classes are correctly defined and inherit from Event.
  2. Ensure that the step functions have parameters annotated with the Event type.

Here is the complete code:

from typing import Union, List
from llama_index.core import SimpleDirectoryReader, VectorStoreIndex
from llama_index.core.response_synthesizers import CompactAndRefine
from llama_index.core.postprocessor.llm_rerank import LLMRerank
from llama_index.core.workflow import (
    Context,
    Workflow,
    StartEvent,
    StopEvent,
    step,
    Event,
)
from llama_index.core.schema import NodeWithScore, QueryType
from llama_index.llms.groq import Groq
from llama_index.embeddings.huggingface import HuggingFaceEmbedding

class RetrieverEvent(Event):
    """Result of running retrieval"""
    nodes: List[NodeWithScore]

class RerankEvent(Event):
    """Result of running reranking on retrieved nodes"""
    nodes: List[NodeWithScore]

class RAGWorkflow(Workflow):
    @step
    async def ingest(self, ctx: Context, ev: StartEvent) -> Union[StopEvent, None]:
        """Entry point to ingest a document, triggered by a StartEvent with `dirname`."""
        dirname = ev.get("dirname")
        if not dirname:
            return None

        documents = SimpleDirectoryReader(dirname).load_data()
        index = VectorStoreIndex.from_documents(
            documents=documents,
            embed_model=HuggingFaceEmbedding(model_name="BAAI/bge-small-en-v1.5"),
        )
        return StopEvent(result=index)

    @step
    async def retrieve(
        self, ctx: Context, ev: StartEvent
    ) -> Union[RetrieverEvent, None]:
        "Entry point for RAG, triggered by a StartEvent with `query`."
        query = ev.get("query")
        index = ev.get("index")

        if not query:
            return None

        print(f"Query the database with: {query}")

        # store the query in the global context
        await ctx.set("query", query)

        # get the index from the global context
        if index is None:
            print("Index is empty, load some documents before querying!")
            return None

        retriever = index.as_retriever(similarity_top_k=2)
        nodes = await retriever.aretrieve(query)
        print(f"Retrieved {len(nodes)} nodes.")
        return RetrieverEvent(nodes=nodes)

    @step
    async def rerank(self, ctx: Context, ev: RetrieverEvent) -> RerankEvent:
        # Rerank the nodes
        ranker = LLMRerank(
            choice_batch_size=5, top_n=3, llm=Groq(model="llama-3.1-70b-versatile")
        )
        print(await ctx.get("query", default=None), flush=True)
        new_nodes = ranker.postprocess_nodes(
            ev.nodes, query_str=await ctx.get("query", default=None)
        )
        print(f"Reranked nodes to {len(new_nodes)}")
        return RerankEvent(nodes=new_nodes)

    @step
    async def synthesize(self, ctx: Context, ev: RerankEvent) -> StopEvent:
        """Return a streaming response using reranked nodes."""
        llm = Groq(model="llama-3.1-70b-versatile")
        summarizer = CompactAndRefine(llm=llm, streaming=True, verbose=True)
        query = await ctx.get("query", default=None)

        response = await summarizer.asynthesize(query, nodes=ev.nodes)
        return StopEvent(result=response)

This should resolve the WorkflowValidationError by ensuring that each step function has a parameter annotated with the Event type [1].

To continue talking to Dosu, mention @dosu.

@plaban1981
Copy link
Author

@dosu

I still encounter : return StopEvent(result=response)

---------------------------------------------------------------------------
WorkflowValidationError                   Traceback (most recent call last)
Cell In[10], [line 25](vscode-notebook-cell:?execution_count=10&line=25)
     [22](vscode-notebook-cell:?execution_count=10&line=22)     """Result of running reranking on retrieved nodes"""
     [23](vscode-notebook-cell:?execution_count=10&line=23)     nodes: List[NodeWithScore]
---> [25](vscode-notebook-cell:?execution_count=10&line=25) class RAGWorkflow(Workflow):
     [26](vscode-notebook-cell:?execution_count=10&line=26)     @step
     [27](vscode-notebook-cell:?execution_count=10&line=27)     async def ingest(self, ctx: Context, ev: StartEvent) -> Union[StopEvent, None]:
     [28](vscode-notebook-cell:?execution_count=10&line=28)         """Entry point to ingest a document, triggered by a StartEvent with `dirname`."""

Cell In[10], [line 27](vscode-notebook-cell:?execution_count=10&line=27)
     [25](vscode-notebook-cell:?execution_count=10&line=25) class RAGWorkflow(Workflow):
     [26](vscode-notebook-cell:?execution_count=10&line=26)     @step
---> [27](vscode-notebook-cell:?execution_count=10&line=27)     async def ingest(self, ctx: Context, ev: StartEvent) -> Union[StopEvent, None]:
     [28](vscode-notebook-cell:?execution_count=10&line=28)         """Entry point to ingest a document, triggered by a StartEvent with `dirname`."""
     [29](vscode-notebook-cell:?execution_count=10&line=29)         dirname = ev.get("dirname")

File c:\Users\PLNAYAK\Documents\workflow\workf\lib\site-packages\llama_index\core\workflow\decorators.py:73, in step(workflow, pass_context, num_workers, *args)
     [70](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:70) if len(args):
     [71](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:71)     # The decorator was used without parentheses, like `@step`
     [72](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:72)     func = args[0]
---> [73](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:73)     decorator(func)
     [74](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:74)     return func
     [75](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:75) return decorator

File c:\Users\PLNAYAK\Documents\workflow\workf\lib\site-packages\llama_index\core\workflow\decorators.py:48, in step.<locals>.decorator(func)
     [46](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:46) # This will raise providing a message with the specific validation failure
     [47](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:47) spec = inspect_signature(func)
---> [48](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:48) validate_step_signature(spec)
     [49](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:49) event_name, accepted_events = next(iter(spec.accepted_events.items()))
     [51](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/decorators.py:51) # store the configuration in the function object

File c:\Users\PLNAYAK\Documents\workflow\workf\lib\site-packages\llama_index\core\workflow\utils.py:96, in validate_step_signature(spec)
     [94](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:94) if num_of_events == 0:
     [95](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:95)     msg = "Step signature must have at least one parameter annotated as type Event"
---> [96](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:96)     raise WorkflowValidationError(msg)
     [97](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:97) elif num_of_events > 1:
     [98](file:///C:/Users/PLNAYAK/Documents/workflow/workf/lib/site-packages/llama_index/core/workflow/utils.py:98)     msg = f"Step signature must contain exactly one parameter of type Event but found {num_of_events}."

WorkflowValidationError: Step signature must have at least one parameter annotated as type Event

Copy link

dosubot bot commented Sep 10, 2024

The WorkflowValidationError: Step signature must have at least one parameter annotated as type Event error indicates that the step functions in your RAGWorkflow class need to have at least one parameter annotated with the Event type.

Here is the corrected code for your RAGWorkflow class:

  1. Ensure that the RetrieverEvent and RerankEvent classes are correctly defined and inherit from Event.
  2. Ensure that the step functions have parameters annotated with the Event type.

Here is the complete code:

from typing import Union, List
from llama_index.core import SimpleDirectoryReader, VectorStoreIndex
from llama_index.core.response_synthesizers import CompactAndRefine
from llama_index.core.postprocessor.llm_rerank import LLMRerank
from llama_index.core.workflow import (
    Context,
    Workflow,
    StartEvent,
    StopEvent,
    step,
    Event,
)
from llama_index.core.schema import NodeWithScore
from llama_index.llms.groq import Groq
from llama_index.embeddings.huggingface import HuggingFaceEmbedding

class RetrieverEvent(Event):
    """Result of running retrieval"""
    nodes: List[NodeWithScore]

class RerankEvent(Event):
    """Result of running reranking on retrieved nodes"""
    nodes: List[NodeWithScore]

class RAGWorkflow(Workflow):
    @step
    async def ingest(self, ctx: Context, ev: StartEvent) -> Union[StopEvent, None]:
        """Entry point to ingest a document, triggered by a StartEvent with `dirname`."""
        dirname = ev.get("dirname")
        if not dirname:
            return None

        documents = SimpleDirectoryReader(dirname).load_data()
        index = VectorStoreIndex.from_documents(
            documents=documents,
            embed_model=HuggingFaceEmbedding(model_name="BAAI/bge-small-en-v1.5"),
        )
        return StopEvent(result=index)

    @step
    async def retrieve(
        self, ctx: Context, ev: StartEvent
    ) -> Union[RetrieverEvent, None]:
        "Entry point for RAG, triggered by a StartEvent with `query`."
        query = ev.get("query")
        index = ev.get("index")

        if not query:
            return None

        print(f"Query the database with: {query}")

        # store the query in the global context
        await ctx.set("query", query)

        # get the index from the global context
        if index is None:
            print("Index is empty, load some documents before querying!")
            return None

        retriever = index.as_retriever(similarity_top_k=2)
        nodes = await retriever.aretrieve(query)
        print(f"Retrieved {len(nodes)} nodes.")
        return RetrieverEvent(nodes=nodes)

    @step
    async def rerank(self, ctx: Context, ev: RetrieverEvent) -> RerankEvent:
        # Rerank the nodes
        ranker = LLMRerank(
            choice_batch_size=5, top_n=3, llm=Groq(model="llama-3.1-70b-versatile")
        )
        print(await ctx.get("query", default=None), flush=True)
        new_nodes = ranker.postprocess_nodes(
            ev.nodes, query_str=await ctx.get("query", default=None)
        )
        print(f"Reranked nodes to {len(new_nodes)}")
        return RerankEvent(nodes=new_nodes)

    @step
    async def synthesize(self, ctx: Context, ev: RerankEvent) -> StopEvent:
        """Return a streaming response using reranked nodes."""
        llm = Groq(model="llama-3.1-70b-versatile")
        summarizer = CompactAndRefine(llm=llm, streaming=True, verbose=True)
        query = await ctx.get("query", default=None)

        response = await summarizer.asynthesize(query, nodes=ev.nodes)
        return StopEvent(result=response)

This should resolve the WorkflowValidationError by ensuring that each step function has a parameter annotated with the Event type [1].

To continue talking to Dosu, mention @dosu.

@plaban1981
Copy link
Author

@dosu,
Now I am able to visualize the workflow in my local environment. I had to make the following changes in utils.py to get it operational


def validate_step_signature(spec: StepSignatureSpec) -> None:
    num_of_events = len(spec.accepted_events)
    num_of_events = 1
    if num_of_events == 0:
        msg = "Step signature must have at least one parameter annotated as type Event"
        raise WorkflowValidationError(msg)
    elif num_of_events > 1:
        msg = f"Step signature must contain exactly one parameter of type Event but found {num_of_events}."
        raise WorkflowValidationError(msg)

    if not spec.return_types:
        msg = f"Return types of workflows step functions must be annotated with their type."
        raise WorkflowValidationError(msg)

@plaban1981
Copy link
Author

@dosu

Here are my Observations :

1. I had installed python 3.11.0 locally and then installed the required llama_index packages. I was able to run the workflow locally and the visualization also happened successfully.

2. In Python 3.9.12 I ahd to make the following changes to make the workflow run successfully and visualize the workflow.

  • "|" operator did not work
  • made chages to num_events =1 in utils.py in lama_index\core\workflow

3. The Visualiztion is simply not working in Colab even with notebook = True parameter.

Could you please have a look at the above observations and advise

Copy link

dosubot bot commented Sep 10, 2024

Your observations highlight a few key points regarding compatibility and functionality across different Python versions and environments. Here are some detailed insights and recommendations based on your observations:

1. Python 3.11.0 Compatibility

  • Observation: The workflow and visualization work correctly with Python 3.11.0.
  • Recommendation: Python 3.11.0 supports the | operator for type annotations, which simplifies type hinting. If possible, use Python 3.11.0 or later for development to leverage these features and avoid compatibility issues.

2. Python 3.9.12 Compatibility

  • Observation: The | operator does not work in Python 3.9.12, and you had to modify num_of_events in utils.py.
  • Recommendation:
    • Type Hinting: Use Union from the typing module instead of the | operator for type annotations in Python 3.9.12.
    • Validation Fix: Modifying num_of_events directly in utils.py is a workaround but not ideal. Instead, ensure that each step function has exactly one parameter annotated with the Event type to pass the validation.

3. Visualization in Google Colab

  • Observation: Visualization is not working in Google Colab even with notebook=True.
  • Recommendation:
    • Ensure Proper Installation: Make sure all required packages are installed in Colab.
      !pip install pyvis llama-index-core llama-index-llms-openai llama-index-embeddings-openai llama-index-readers-file llama-index-utils-workflow
    • Check HTML Rendering: Use the following approach to render the HTML content in Colab:
      from pyvis.network import Network
      from IPython.core.display import display, HTML
      
      def draw_all_possible_flows(
          workflow: Workflow,
          filename: str = "workflow_all_flows.html",
          notebook: bool = True,  # Set notebook to True
      ) -> None:
          """Draws all possible flows of the workflow."""
          net = Network(directed=True, height="750px", width="100%")
      
          # Add the nodes + edge for stop events
          net.add_node(
              StopEvent.__name__,
              label=StopEvent.__name__,
              color="#FFA07A",
              shape="ellipse",
          )
          net.add_node("_done", label="_done", color="#ADD8E6", shape="box")
          net.add_edge(StopEvent.__name__, "_done")
      
          # Add nodes from all steps
          steps = get_steps_from_class(workflow)
          if not steps:
              # If no steps are defined in the class, try to get them from the instance
              steps = get_steps_from_instance(workflow)
      
          step_config: Optional[StepConfig] = None
          for step_name, step_func in steps.items():
              step_config = getattr(step_func, "__step_config", None)
              if step_config is None:
                  continue
      
              net.add_node(
                  step_name, label=step_name, color="#ADD8E6", shape="box"
              )  # Light blue for steps
      
              for event_type in step_config.accepted_events:
                  net.add_node(
                      event_type.__name__,
                      label=event_type.__name__,
                      color="#90EE90" if event_type != StartEvent else "#E27AFF",
                      shape="ellipse",
                  )  # Light green for events
      
          # Add edges from all steps
          for step_name, step_func in steps.items():
              step_config = getattr(step_func, "__step_config", None)
      
              if step_config is None:
                  continue
      
              for return_type in step_config.return_types:
                  if return_type != type(None):
                      net.add_edge(step_name, return_type.__name__)
      
              for event_type in step_config.accepted_events:
                  net.add_edge(event_type.__name__, step_name)
      
          if notebook:
              net.show(filename, notebook=True)
              with open(filename, "r") as file:
                  display(HTML(file.read()))
          else:
              net.show(filename)
      
      # Example usage in Google Colab
      draw_all_possible_flows(
          RAGWorkflow, filename="multi_step_workflow.html", notebook=True
      )

Summary

  • Python 3.11.0: Works well with the | operator and the workflow visualization.
  • Python 3.9.12: Use Union for type annotations and ensure proper event parameter annotations to avoid modifying utils.py.
  • Google Colab: Ensure all dependencies are installed and use the provided method to render HTML content.

By following these recommendations, you should be able to achieve consistent functionality across different environments and Python versions.

To continue talking to Dosu, mention @dosu.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working triage Issue needs to be triaged/prioritized
Projects
None yet
Development

No branches or pull requests

1 participant