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

Hierarchical Agent Teams with Ollama #196

Open
4 tasks done
ZappaBoy opened this issue Mar 9, 2024 · 6 comments
Open
4 tasks done

Hierarchical Agent Teams with Ollama #196

ZappaBoy opened this issue Mar 9, 2024 · 6 comments
Labels
enhancement New feature or request Notebook Request

Comments

@ZappaBoy
Copy link

ZappaBoy commented Mar 9, 2024

Checked other resources

  • I added a very descriptive title to this issue.
  • I searched the LangChain documentation with the integrated search.
  • I used the GitHub search to find a similar question and didn't find it.
  • I am sure that this is a bug in LangChain rather than my code.

Example Code

import functools
import operator
from datetime import datetime
from textwrap import dedent
from typing import Sequence, TypedDict, Annotated

from langchain.agents import create_react_agent, AgentExecutor
from langchain_community.tools.ddg_search import DuckDuckGoSearchRun
from langchain_core.messages import HumanMessage, BaseMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables import Runnable
from langchain_core.tools import BaseTool, tool
from langchain_experimental.llms.ollama_functions import OllamaFunctions
from langchain_community.output_parsers.ernie_functions import JsonOutputFunctionsParser
from langchain_experimental.tools import PythonREPLTool
from langgraph.graph import END, StateGraph

SUPERVISOR = 'Supervisor'
RESEARCHER = 'Researcher'
CODER = 'Coder'
FINISH = 'FINISH'
members = [RESEARCHER, CODER]


class GraphState(TypedDict):
    messages: Annotated[Sequence[BaseMessage], operator.add]
    next: str


def create_agent(llm: OllamaFunctions, tools: Sequence[BaseTool], system_prompt: str) \
        -> Runnable:
    prompt = ChatPromptTemplate.from_messages(
        [
            (
                "system",
                system_prompt,
            ),
            MessagesPlaceholder(variable_name="messages"),
            MessagesPlaceholder(variable_name="agent_scratchpad"),
            MessagesPlaceholder(variable_name="tools"),
            MessagesPlaceholder(variable_name="tool_names"),
        ]
    )

    return create_react_agent(llm, tools, prompt)


def create_agent_executor(llm: OllamaFunctions, tools: Sequence[BaseTool],
                          system_prompt: str) -> AgentExecutor:
    agent = create_agent(llm, tools, system_prompt)
    executor = AgentExecutor(agent=agent, tools=tools)
    return executor


def agent_node(state: TypedDict, agent: Runnable, name: str):
    result = agent.invoke(state)
    return {"messages": [HumanMessage(content=result["output"], name=name)]}


@tool
def get_actual_date_tool(date_format: str = "%Y-%m-%d %H:%M:%S"):
    """
    Get the current time
    """
    return datetime.now().strftime(date_format)


def get_code_executor_code() -> BaseTool:
    return PythonREPLTool()


def get_web_search_tool() -> BaseTool:
    return DuckDuckGoSearchRun()


llm = OllamaFunctions(model="openhermes")

options = [FINISH] + members

function_def = {
    "name": "route",
    "description": "Select the next role.",
    "parameters": {
        "title": "routeSchema",
        "type": "object",
        "properties": {
            "next": {
                "title": "Next",
                "anyOf": [
                    {"enum": options},
                ],
            }
        },
        "required": ["next"],
    },
}
prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            dedent("""
                You are a supervisor tasked with managing a conversation between the following workers: 
                {members}. Given the following user request, respond with the worker to act next. Each worker 
                will perform a task and respond with their results and status. When finished, respond with 
                FINISH.
            """)
        ),
        MessagesPlaceholder(variable_name="messages"),
        (
            "system",
            dedent("""
                Given the conversation above, who should act next? Or should we FINISH? Select one of: {options}
            """)
        ),
    ]
).partial(options=str(options), members=", ".join(members))

supervisor_chain = (
        prompt
        | llm.bind(functions=[function_def], function_call={"name": "route"})
        | JsonOutputFunctionsParser()
)

research_agent = create_agent_executor(
    llm,
    [get_web_search_tool()],
    "You are a web researcher."
)
research_node = functools.partial(agent_node, agent=research_agent, name=RESEARCHER)

code_agent = create_agent_executor(
    llm,
    [get_code_executor_code()],
    "You may generate safe python code to analyze data and generate charts using matplotlib.",
)
code_node = functools.partial(agent_node, agent=code_agent, name=CODER)

workflow_graph = StateGraph(GraphState)
workflow_graph.add_node(RESEARCHER, research_node)
workflow_graph.add_node(CODER, code_node)
workflow_graph.add_node(SUPERVISOR, supervisor_chain)

for member in members:
    # We want our workers to ALWAYS "report back" to the supervisor when done
    workflow_graph.add_edge(member, SUPERVISOR)
# The supervisor populates the "next" field in the graph state
# which routes to a node or finishes
conditional_map = {k: k for k in members}
conditional_map[FINISH] = END
workflow_graph.add_conditional_edges(SUPERVISOR, lambda x: x["next"], conditional_map)
# Finally, add entrypoint
workflow_graph.set_entry_point(SUPERVISOR)

chain = workflow_graph.compile()

running = True
while running:
    user_input = input("Enter text (press 'q' or ctrl-c to quit): ")
    if user_input.lower() == 'q':
        running = False
    try:
        inputs = {
            "messages": [
                HumanMessage(content=user_input)
            ]
        }
        for s in chain.stream(inputs, {"recursion_limit": 100}, ):
            if "__end__" not in s:
                print("---")
                result = list(s.values())[0]
                print(result)
    except Exception as e:
        print(e)
        print('Sorry, something goes wrong. Try with a different input')

Error Message and Stack Trace (if applicable)

The output is copied directly from LangSmith, the stacktrace is truncated both in console and LangSmith.

ValueError('variable agent_scratchpad should be a list of base messages, got ')Traceback (most recent call last):

File ".../langchain_core/runnables/base.py", line 1262, in _call_with_config
context.run(

File ".../langchain_core/runnables/config.py", line 326, in call_func_with_variable_args
return func(input, **kwargs) # type: ignore[call-arg]
^^^^^^^^^^^^^^^^^^^^^

File ".../langchain_core/prompts/base.py", line 103, in _format_prompt_with_error_handling
return self.format_prompt(**inner_input)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

File ".../langchain_core/prompts/chat.py", line 535, in format_prompt
messages = self.format_messages(**kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

File ".../langchain_core/prompts/chat.py", line 797, in format_messages
message = message_template.format_messages(**kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

File ".../langchain_core/prompts/chat.py", line 129, in format_messages
raise ValueError(

ValueError: variable agent_scratchpad should be a list of base messages, got

Description

I'm trying to reproduce the provided example hierarchical_agent_teams.ipynb using Ollama with OllamaFunctions

I solved some incompatibility issues found with the bind_function replaced with bind and passing explicitly the "name" of the route function ({"name": "route"} instead of "route").

After that, I'm stuck on this error: 'variable agent_scratchpad should be a list of base messages, got. The output seems to be truncated but this happens because the agent_scratchpad variable is empty ('').
How this can be solved? Am I making some error in the GraphState or something else?

Thanks in advance for the support.

System Info

$ python -m langchain_core.sys_info

System Information

OS: Linux
OS Version: #1 SMP PREEMPT_DYNAMIC Sun, 03 Mar 2024 07:25:31 +0000
Python Version: 3.11.7 (main, Dec 14 2023, 11:23:37) [GCC 13.2.1 20230801]

Package Information

langchain_core: 0.1.30
langchain: 0.1.11
langchain_community: 0.0.26
langsmith: 0.1.23
langchain_experimental: 0.0.53
langchain_openai: 0.0.8
langchain_text_splitters: 0.0.1
langchainhub: 0.1.15
langgraph: 0.0.26

Packages not installed (Not Necessarily a Problem)

The following packages were not found:

langserve

@hinthornw hinthornw added enhancement New feature or request Notebook Request labels Mar 12, 2024
@hinthornw
Copy link
Contributor

hinthornw commented Mar 12, 2024

Would happily review a PR of a notebook that reliably implements this with OSS models!

My laptop crashes anytime I try to run anything > 1B parameters in size

@ZappaBoy
Copy link
Author

Would happily review a PR of a notebook that reliably implements this with OSS models!

My laptop crashes anytime I try to run anything > 1B parameters in size

I am trying to modify OllamaFunctions to allow the use of routes. I fixed some issues but now I'm facing again with the agent_scratchpad empty variable. Can you explain in which phase/place/cases this variable needs to be set and which is the content?

@shashankbist37
Copy link

shashankbist37 commented Apr 18, 2024

The approach that proved effective for me, which you might consider, involves removing the elements from the message placeholder. The reason behind this is that when you input them as a message placeholder, the system interprets them as a string. However, our requirement is for a list format.

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
prompt = ChatPromptTemplate.from_messages(
            [
                (
                    "system",
                    system_prompt+"""  
                     You are a helpful assistant. You have access to the following tools:

                {tools}
                
                Use the following format:

                Question: the input question you must answer
                Thought: you should always think about what to do
                Action: the action to take, should be one of [{tool_names}]
                Action Input: the input to the action
                Observation: the result of the action
                Thought: I now know the final answer
                Final Answer: the final answer to the original input question

                Begin!
                Previous conversation history:``¡¡
   
                Thought: {agent_scratchpad}"""
                ),
                MessagesPlaceholder(variable_name="messages"),
            ]
        )
        agent = create_react_agent(llm, tools, prompt)
        executor = AgentExecutor(agent=agent, tools=tools, verbose=True,handle_parsing_errors=True)
        return executor
        

@ChristianSch
Copy link

ChristianSch commented Apr 23, 2024

The same issue happens when replacing OpenAI with antropic. I'm at a loss because I ran out of OpenAI allowed use and none of the multi-agent notebooks work for me with anthropic models.

The error message is slightly different: ValueError: System message must be at beginning of message list.

You can check this repo for the exact code I'm using: https://github.com/ChristianSch/agents/blob/main/multi-agent/agent_supervisor.py

@shashankbist37 's recommendation to move everything to simple format placeholders did not work out for me.

@hinthornw
Copy link
Contributor

You can likely replace the system messages with user messages

@hinthornw
Copy link
Contributor

Pushed some updates but will do some more checks when i find time

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request Notebook Request
Projects
None yet
Development

No branches or pull requests

4 participants