langflow-ai / langflow

⛓️ Langflow is a visual framework for building multi-agent and RAG applications. It's open-source, Python-powered, fully customizable, model and vector store agnostic.
http://www.langflow.org
MIT License
22.45k stars 3.2k forks source link

Incorrect Usage of Flow as Tool #2425

Closed qiaolb closed 2 days ago

qiaolb commented 3 weeks ago

Describe the bug When invoking the Flow as Tool through the Tool Call Agent, the system displayed an error message: TypeError: sequence item 0: expected str instance, dict found

│ /Users/xxx/miniconda3/lib/python3.11/site-packages/langflow/base/tools/flow_tool.py:113 in _arun │            
│                                                                                                       │            
│   110 │   │   │   for output in run_output.outputs:                                                   │            
│   111 │   │   │   │   if output:                                                                      │            
│   112 │   │   │   │   │   data.extend(build_data_from_result_data(output,                             │            
│       get_final_results_only=self.get_final_results_only))                                            │            
│ ❱ 113 │   │   return format_flow_output_data(data)                                                    │            
│   114                                                                                                 │            
│                                                                                                       │            
│ /Users/xxx/miniconda3/lib/python3.11/site-packages/langflow/base/flow_processing/utils.py:91 in  │            
│ format_flow_output_data                                                                               │            
│                                                                                                       │            
│   89 │   """                                                                                          │            
│   90 │   result = "Flow run output:\n"                                                                │            
│ ❱ 91 │   results = "\n".join([value.result for value in data if value.data["message"]])               │            
│   92 │   return result + results                                                                      │            
│   93                                                                                                  │            
╰───────────────────────────────────────────────────────────────────────────────────────────────────────╯            
TypeError: sequence item 0: expected str instance, dict found 

Browser and Version

To Reproduce Steps to reproduce the behavior:

  1. Create a flow: A
  2. Create a flow: B, and use Flow as Tool for A
  3. Run
  4. See error

Additional context The issue was resolved after I made the following modifications to the code. Details are as follows:

langflow/base/flow_processing/utils.py, line 91:

Before the modification:

results = "\n".join([value.result for value in data if value.data["message"]])

After the modification:

results = "\n".join([value.message["message"] for value in data if value.data["message"]])
dosubot[bot] commented 3 weeks ago

To resolve the TypeError: sequence item 0: expected str instance, dict found error, you need to ensure that all items in the sequence being joined are strings. The error typically occurs when you try to join a list of dictionaries or other non-string items.

In the provided code, the issue likely arises when converting the result to a string. Specifically, the result["output"] might be a dictionary or a list of dictionaries instead of a string.

To fix this, you should convert the result["output"] to a string before returning it. Here is the modified part of the run_agent method:

async def run_agent(self) -> Message:
    if "input" not in self.user_prompt:
        raise ValueError("Prompt must contain 'input' key.")
    messages = [
        ("system", self.system_prompt),
        ("placeholder", "{chat_history}"),
        ("human", self.user_prompt),
        ("placeholder", "{agent_scratchpad}"),
    ]
    prompt = ChatPromptTemplate.from_messages(messages)
    agent = create_tool_calling_agent(self.llm, self.tools, prompt)

    runnable = AgentExecutor.from_agent_and_tools(
        agent=cast(BaseSingleActionAgent, agent),
        tools=self.tools,
        verbose=True,
        handle_parsing_errors=self.handle_parsing_errors,
    )
    input_dict: dict[str, str | list[Dict[str, str]]] = {"input": self.input_value}
    if hasattr(self, "memory") and self.memory:
        input_dict["chat_history"] = self.convert_chat_history(self.memory)
    result = await runnable.ainvoke(input_dict)
    self.status = result

    if "output" not in result:
        raise ValueError("Output key not found in result. Tried 'output'.")

    # Ensure result_string is a string
    result_string = str(result["output"])

    return Message(text=result_string)

By converting result["output"] to a string using str(result["output"]), you ensure that the Message object receives a string, thus avoiding the TypeError [1].

Additionally, ensure that the user_prompt contains the 'input' key as required by the ToolCallingAgentComponent class:

async def build(
    self,
    input_value: str,
    llm: BaseLanguageModel,
    tools: List[Tool],
    user_prompt: str = "{input}",
    message_history: Optional[List[Record]] = None,
    system_message: str = "You are a helpful assistant",
    handle_parsing_errors: bool = True,
) -> Text:
    if "input" not in user_prompt:
        raise ValueError("Prompt must contain 'input' key.")
    messages = [
        ("system", system_message),
        ("placeholder", "{chat_history}"),
        ("human", user_prompt),
        ("placeholder", "{agent_scratchpad}"),
    ]
    prompt = ChatPromptTemplate.from_messages(messages)
    agent = create_tool_calling_agent(llm, tools, prompt)
    result = await self.run_agent(agent, input_value, tools, message_history, handle_parsing_errors)
    self.status = result
    return result

This ensures that the prompt is correctly formatted and the agent can process the input without errors [1].

In the context of using the Flow as Tool through the Tool Call Agent, ensure that the FlowToolComponent is correctly configured and that the result["output"] is properly handled. The FlowToolComponent class is designed to construct a Tool from a function that runs the loaded Flow, and it includes methods to retrieve and manage flows:

class FlowToolComponent(CustomComponent):
    display_name = "Flow as Tool"
    description = "Construct a Tool from a function that runs the loaded Flow."
    field_order = ["flow_name", "name", "description", "return_direct"]
    trace_type = "tool"
    beta = True

    def get_flow_names(self) -> List[str]:
        flow_datas = self.list_flows()
        return [flow_data.data["name"] for flow_data in flow_datas]

    def get_flow(self, flow_name: str) -> Optional[Data]:
        flow_datas = self.list_flows()
        for flow_data in flow_datas:
            if flow_data.data["name"] == flow_name:
                return flow_data
        return None

    def update_build_config(self, build_config: dotdict, field_value: Any, field_name: str | None = None):
        logger.debug(f"Updating build config with field value {field_value} and field name {field_name}")
        if field_name == "flow_name":
            build_config["flow_name"]["options"] = self.get_flow_names()

        return build_config

    def build_config(self):
        return {
            "flow_name": {
                "display_name": "Flow Name",
                "info": "The name of the flow to run.",
                "options": [],
                "real_time_refresh": True,
                "refresh_button": True,
            },
            "name": {
                "display_name": "Name",
                "description": "The name of the tool.",
            },
            "description": {
                "display_name": "Description",
                "description": "The description of the tool.",
            },
            "return_direct": {
                "display_name": "Return Direct",
                "description": "Return the result directly from the Tool.",
                "advanced": True,
            },
        }

    async def build(self, flow_name: str, name: str, description: str, return_direct: bool = False) -> Tool:
        FlowTool.update_forward_refs()
        flow_data = self.get_flow(flow_name)
        if not flow_data:
            raise ValueError("Flow not found.")
        graph = Graph.from_payload(flow_data.data["data"])
        inputs = get_flow_inputs(graph)
        tool = FlowTool(
            name=name,
            description=description,
            graph=graph,
            return_direct=return_direct,
            inputs=inputs,
            flow_id=str(flow_data.id),
            user_id=str(self._user_id),
        )
        description_repr = repr(tool.description).strip("'")
        args_str = "\n".join([f"- {arg_name}: {arg_data['description']}" for arg_name, arg_data in tool.args.items()])
        self.status = f"{description_repr}\nArguments:\n{args_str}"
        return tool  # type: ignore

Ensure that the FlowToolComponent is properly instantiated and that the result["output"] is correctly converted to a string before being used [1].

To continue talking to Dosu, mention @dosu.

carlosrcoelho commented 1 week ago

Hi @qiaolb Could you try using the new version 1.0.10 and see if the error still occurs?

carlosrcoelho commented 2 days ago

Thank you for your contribution! This issue will be closed. If you have any questions or encounter another problem, please open a new issue and we will be ready to assist you.