langchain-ai / langchain

🦜🔗 Build context-aware reasoning applications
https://python.langchain.com
MIT License
88.43k stars 13.89k forks source link

Use OllamaFunctions to build AgentExecutor but return errors with tools #22266

Open DiaQusNet opened 1 month ago

DiaQusNet commented 1 month ago

Checked other resources

Example Code


from langchain_experimental.llms.ollama_functions import OllamaFunctions
from langchain_core.tools import tool
from langchain_community.tools.tavily_search import TavilySearchResults
import os
from langchain.agents import AgentExecutor
from langchain.agents import create_tool_calling_agent
from langchain import hub

os.environ["TAVILY_API_KEY"] = ''

llm = OllamaFunctions(model="llama3:8b", temperature=0.6, format="json")

@tool
def multiply(first_int: int, second_int: int) -> int:
    """Multiply two integers together."""
    return first_int * second_int

@tool
def search_in_web(query: str) -> str:
    """Use Tavily to search information in Internet."""
    search = TavilySearchResults(max_results=2)
    context = search.invoke(query)
    result = ""
    for i in context:
        result += f"In site:{i['url']}, context shows:{i['content']}.\n"
    return result

tools=[
        {
            "name": "multiply",
            "description": "Multiply two integers together.",
            "parameters": {
                "type": "object",
                "properties": {
                    "first_int": {
                        "type": "integer",
                        "description": "The first integer number to be multiplied. " "e.g. 4",
                    },
                    "second_int": {
                        "type": "integer",
                        "description": "The second integer to be multiplied. " "e.g. 7",
                    },
                },
                "required": ["first_int", "second_int"],
            },
        },
        {
            "name": "search_in_web",
            "description": "Use Tavily to search information in Internet.",
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {
                        "type": "str",
                        "description": "The query used to search in Internet. " "e.g. what is the weather in San Francisco?",
                    },
                },
                "required": ["query"],
            },
        }]

prompt = hub.pull("hwchase17/openai-functions-agent")

agent = create_tool_calling_agent(llm, tools, prompt)

agent_executor = AgentExecutor(agent=agent, tools=tools)

### Error Message and Stack Trace (if applicable)

---------------------------------------------------------------------------
Traceback (most recent call last):
  File "/media/user/My Book/LLM/Agent/check.py", line 80, in <module>
    agent_executor = AgentExecutor(agent=agent, tools=tools)
  File "/home/user/anaconda3/envs/XIE/lib/python3.9/site-packages/pydantic/v1/main.py", line 339, in __init__
    values, fields_set, validation_error = validate_model(__pydantic_self__.__class__, data)
  File "/home/user/anaconda3/envs/XIE/lib/python3.9/site-packages/pydantic/v1/main.py", line 1100, in validate_model
    values = validator(cls_, values)
  File "/home/user/anaconda3/envs/XIE/lib/python3.9/site-packages/langchain/agents/agent.py", line 981, in validate_tools
    tools = values["tools"]
KeyError: 'tools'

### Description

I am tring to initialize AgentExecutor with OllamaFunctions.
I have check the `OllamaFunctions.bind_tools` and it works well. 
So I want to use AgentExecutor to let llm to response.
But `keyerror: tools ` confused me, since the `create_tool_calling_agent` use the same `tools` value.
Does anyone know how to fix this problem?

### System Info

langchain==0.2.1
langchain-community==0.0.38
langchain-core==0.2.1
langchain-experimental==0.0.58
langchain-openai==0.1.7
langchain-text-splitters==0.2.0
langchainhub==0.1.16
platform: Ubuntu 20.04
python==3.9
keenborder786 commented 1 month ago

Well you cannot use OllamaFunctions with AgentExecutor due to unsupported messages type in ChatOllama (ToolAgentAction) but you can still use OllamaFunction to call functions by doing some custom changes as shown below, let me know if you understand the code below because I have made some major changes in OllamaFunctions to make it work. Please try it on your machine and let me know if it works. I would also suggest using React which is being powered by Ollama:


from langchain_experimental.llms.ollama_functions import OllamaFunctions,convert_to_ollama_tool
from langchain_core.prompts import ChatPromptTemplate

from langchain_core.tools import tool
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_core.pydantic_v1 import (
    BaseModel,
    Field
)
from langchain_core.messages import AIMessage, ToolCall
from langchain_core.outputs import ChatGeneration, ChatResult
from langchain_core.prompts import SystemMessagePromptTemplate,HumanMessagePromptTemplate
from langchain_core.pydantic_v1 import BaseModel
from langchain_community.chat_models.ollama import ChatOllama
from langchain.agents.output_parsers import ToolsAgentOutputParser

class CustomOllamaFunctions(OllamaFunctions):

    def _generate(
        self,
        messages,
        stop,
        run_manager,
        **kwargs,
    ):
        functions = kwargs.get("functions", [])
        if "functions" in kwargs:
            del kwargs["functions"]
        if "function_call" in kwargs:
            functions = [
                fn for fn in functions if fn["name"] == kwargs["function_call"]["name"]
            ]
            if not functions:
                raise ValueError(
                    "If `function_call` is specified, you must also pass a "
                    "matching function in `functions`."
                )
            del kwargs["function_call"]   
        response_message = ChatOllama(model="llama3", temperature=0, format="json")._generate(
            messages, stop=stop, run_manager=run_manager, **kwargs
        )
        chat_generation_content = response_message.generations[0].text
        if not isinstance(chat_generation_content, str):
            raise ValueError("OllamaFunctions does not support non-string output.")
        try:
            parsed_chat_result = json.loads(chat_generation_content)
        except json.JSONDecodeError:
            raise ValueError(
                f"""'{self.model}' did not respond with valid JSON. 
                Please try again. 
                Response: {chat_generation_content}"""
            )
        called_tool_name = parsed_chat_result["tool"]
        called_tool = next(
            (fn for fn in functions if fn["name"] == called_tool_name), None
        )
        if called_tool is None:
            raise ValueError(
                f"Failed to parse a function call from {self.model} output: "
                f"{chat_generation_content}"
            )
        called_tool_arguments = parsed_chat_result["tool_input"]

        response_message_with_functions = AIMessage(
            content="",
            tool_calls=[
                ToolCall(
                    name=called_tool_name,
                    args=called_tool_arguments if called_tool_arguments else {},
                    id=f"call_{str(uuid.uuid4()).replace('-', '')}",
                )
            ],
        )
        return ChatResult(
            generations=[ChatGeneration(message=response_message_with_functions)]
        )

os.environ["TAVILY_API_KEY"] = ''

class MultiplyInput(BaseModel):
    """Input for the Multiply tool."""

    name = "Multiply"
    description = "Multiply two integers together."
    first_int: int = Field(description="The first integer number to be multiplied e.g 4")
    second_int: int = Field(description="The second integer to be multiplied. e.g 7")

class SearchinWebInput(BaseModel):
    """Input for the Multiply tool."""

    name = "SearchinWebInput"
    description = "Use Tavily to search information in Internet."
    query: str = Field(description="The query used to search in Internet. " "e.g. what is the weather in San Francisco?")

def multiply(first_int: int, second_int: int) -> int:
    """Multiply two integers together."""
    return first_int * second_int

def search_in_web(query: str) -> str:
    """Use Tavily to search information in Internet."""
    search = TavilySearchResults(max_results=2)
    context = search.invoke(query)
    result = ""
    for i in context:
        result += f"In site:{i['url']}, context shows:{i['content']}.\n"
    return result

llm = CustomOllamaFunctions(model="llama3", temperature=0, format="json")
tools = [MultiplyInput,SearchinWebInput]
tools = [convert_to_ollama_tool(fn) for fn in tools]

system_message = """
You have access to the following tools to answer the user question:

{tools}

You must always select one of the above tools and respond with only a JSON object matching the following schema:

{{
  "tool": <name of the selected tool>,
  "tool_input": <parameters for the selected tool, matching the tool's JSON schema>
}}

Double check that in your json, you only have `tool` and `tool_input`.
""".format(tools=json.dumps(tools, indent=2))

prompt = ChatPromptTemplate(messages=[SystemMessagePromptTemplate.from_template(system_message,template_format='jinja2'),
                                      HumanMessagePromptTemplate.from_template('{input}')])

all_tools = {'Multiply':multiply,'SearchinWebInput':search_in_web}
llm_with_tools = llm.bind_tools(tools=tools)
agent = prompt | llm_with_tools | ToolsAgentOutputParser()
action = agent.invoke({'input':'Using the search tool tell me Who is elon musk?'})[0]
tool_used,tool_input = action.tool,action.tool_input
print(all_tools[tool_used](**tool_input))
DiaQusNet commented 1 month ago

@keenborder786 Agent successfully returned search information with your codes. Thanks. I know the complexity is due to Ollama has not support function calling yet. So OllamaFunctions will continue to imporve? It will be easier to build agent if Ollama support function calling in the future?

keenborder786 commented 1 month ago

@DiaQusNet Agreed therefore keep the issue open

noshila commented 1 month ago

@keenborder786 Thank you. Is it possible to use tools with llama.cpp as well? because i don't think there is anything in llama.cpp like ollamafunctions. also, i prefer llama.cpp because it is faster, almost twice, for same model.

though i'm able to use kobold.cpp for tools, however, it only works for first step "AI message" and does not go to tool message, and did not throw error like ollama or llama.cpp, so I assumed that maybe tools can be used with kobold.cpp however, they don't work like I want, or perhaps tools don't work at all and kobold just don't throw error.

noshila commented 1 month ago

update with llama.cpp, now it is not throwing error after updating llama.cpp. However, now the output is like the kobold.cpp's

(ai) D:\work\project\ai>python agent1.py

{
    'agent': {
        'messages': [
            AIMessage(
                content=" To answer your question, I'll first list the tables in the database and then query the schema of the table
that seems most relevant to find out which country's customers spent the most.\n\n```sql\nPRAGMA
table_info(sqlite_master);\n```\n\nLet's assume we have a table named `transactions` that contains information about customer
transactions. Now, I will query the schema of this table to find out which columns are available:\n\n```sql\nPRAGMA
table_info(transactions);\n```\n\nAssuming the `transactions` table has columns like `customer_id`, `amount`, and `country`, I'll
write a SQL query to find the country with the highest total spent by its customers.\n\n```sql\nSELECT country, SUM(amount) as
total_spent\nFROM transactions\nGROUP BY country\nORDER BY total_spent DESC\nLIMIT 1;\n```\n\nThis query groups all transactions by
their respective countries and calculates the total amount spent in each country. It then orders the results in descending order
based on the total spent, and limits the output to only the top result (the country with the highest total spent).",
                response_metadata={
                    'token_usage': {'completion_tokens': 263, 'prompt_tokens': 265, 'total_tokens': 528},
                    'model_name': 'gemini-pro',
                    'system_fingerprint': None,
                    'finish_reason': 'stop',
                    'logprobs': None
                },
                id='run-45dbe68d-a9a6-42af-b355-a363e5c74406-0',
                usage_metadata={'input_tokens': 265, 'output_tokens': 263, 'total_tokens': 528}
            )
        ]
    }
}
----

This is from the code of agent part from the page agents but i think output should be "agent"... action ... agent ... action ... until you get the correct answer

lalanikarim commented 2 weeks ago

Take a look at #22339 which should have addressed this issue. The PR was approved and merged yesterday but a release is yet to be cut from it and should happen in the next few days.

In the meantime, you may try and install langchain-experimental directly from langchain's source like this:

pip install git+https://github.com/langchain-ai/langchain.git\#egg=langchain-experimental\&subdirectory=libs/experimental

I hope this helps.