LangChain: Allow LLMs to Interact with Your Code
Learn how to implement custom functions for your LLM, using tools.
June 20, 2024 · LangChain, LLM
Introduction
Generative models are under everyone’s attention. Many AI applications now no longer require Machine Learning experts in the field but simply know how to Implement API calls.
Recently, for example, I participated in a hackathon, and I had to implement a custom-named entity recognition, but I directly used an LLM and exploited its few-shot learner capability to get the result I wanted, which to win the hackathon was quite enough! (You can check the project here if you want).
So for many real-world applications, the focus is shifting more toward how to interact and use these LLMs rather than creating models. LangChain is a library that allows you to do just that, and I’ve written several articles about it lately.
LangChain Tools
Tools are utilities that an LLM can use to augment its capabilities. Tools can be instantiated within chains or agents.
For example, an LLM might conduct a Wikipedia search before responding to ensure an up-to-date response.
Of course, an agent can use multiple tools, and so often what is done is to define a list of tools.
Let us now look at the anatomy of a tool. A tool is nothing more than a class consisting of several fields:
- name (str): defines a unique name of the tool
- description (str): a description of the tool’s utility in natural language. The LLM will be able to read this description and figure out whether or not it will need the tool to answer the query.
- return_direct (bool): A tool might return the output of a custom function for example. Do we want that output to be presented directly to the user (True) or to be preprocessed by the LLM (False)?
- args_schema (Pydantic BaseModel): For example, the tool might use a custom function whose input parameters must be retrieved from the user’s query. We can provide more information about each parameter so that the LLM can do this step more easily.
How to define a tool
There are multiple approaches to defining a tool that we will look at in this article. First, we import the necessary libraries and instantiate an OpenAI model.
To do this you will need a token, you can see in my previous article how to get it.
!pip install langchain
!pip install openai
from langchain import LLMMathChain, SerpAPIWrapper
from langchain.agents import AgentType, initialize_agent
from langchain.chat_models import ChatOpenAI
from langchain.tools import BaseTool, StructuredTool, Tool, tool
import os
os.environ["OPENAI_API_KEY"] = ... # insert your API_TOKEN here
llm = ChatOpenAI(temperature=0)
The first way to instantiate a tool is to use the Tool class.
Suppose we want to give the tool the ability to search the web for information, to do this we will use some Google API called SerpAPI, you can register and get the API here: https://serpapi.com/
Let’s instantiate a SerpAPIWrapper class, and define the tool with the from_function method.
In the func field, we need to put a pointer to the method we want to launch using this tool, which is the run method of SerpAPI. And as we have seen we give a name and description to the tool. It is easier to do than explain it.
search = SerpAPIWrapper()
tools = [
Tool.from_function(
func=search.run,
name="Search",
description="useful for when you need to answer questions about current events"
),
]
Now we can provide our agent with the list of tools created, in this case only one.
agent = initialize_agent(
tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True
)
agent.run(
"Who is Bob Dylan's girlfriend?
)
Custom Tools
The clearest method in my opinion for creating a custom tool is to inherit the BaseTool class.
class CustomTool(BaseTool):
name = "custom_tool"
description = "useful for when you need to answer questions about medium articles"
def _run(
self, query: str, run_manager: Optional[CallbackManagerForToolRun] = None
) -> str:
"""Use the tool."""
return "I am not a Medium expert, but I know that Marcello is pretty good! :I)"
async def _arun(
self, query: str, run_manager: Optional[AsyncCallbackManagerForToolRun] = None
) -> str:
"""Use the tool asynchronously."""
raise NotImplementedError("custom_search does not support async")
You see this is the implementation of a custom tool, which will be used whenever the user asks a question regarding Medium. However, the returned string will not be exactly what I set up because it will be processed further by the Large Language Model.
If we want to return something directly just add a “return_direct” field in the following way.
class CustomTool(BaseTool):
name = "custom_tool"
description = "useful for when you need to answer questions about medium articles"
return_direct=True
def _run(
self, query: str, run_manager: Optional[CallbackManagerForToolRun] = None
) -> str:
"""Use the tool."""
return "I am not a Medium expert, but I know that Marcello is pretty good! :I)"
async def _arun(
self, query: str, run_manager: Optional[AsyncCallbackManagerForToolRun] = None
) -> str:
"""Use the tool asynchronously."""
raise NotImplementedError("custom_search does not support async")
Even if we don’t use the _arun method (which is useful for asynchronous calling) we still have to implement it because BaseTool is an abstract class, and if we don’t implement all the abstract methods we’ll get an error.
Real Life Example
One day a friend of mine said, “Hey Marcello, since you do AI and that kind of stuff, why don’t you make me a chatbot, which when required returns the doctors’ working hours, and books appointments?”
The first thing I thought of to solve this problem is to use LangChain and let the LLM interact with the user, and then as soon as the model understands that the user has requested to see the working hours it will just return a CSV file (or dataframe if you like).
So the same thing can be used for this use case as well. Suppose we have a CSV file called, work_time.csv
import pandas as pd
class WorkingHours(BaseTool):
name = "working_hours"
description = "useful for when you need to answer questions about working hours of the medical staff"
return_direct=True+
def _run(
self, query: str, run_manager: Optional[CallbackManagerForToolRun] = None
) -> str:
"""Use the tool."""
df = pd.read_csv("working_hours.csv") #maybe you need to retieve some real time data from a DB
return df
async def _arun(
self, query: str, run_manager: Optional[AsyncCallbackManagerForToolRun] = None
) -> str:
"""Use the tool asynchronously."""
raise NotImplementedError("custom_search does not support async")
And just like that, a prototype of the app my friend wanted is ready in just a few lines of code! Obviously, work with a good front-end developer to make it look better!
Final Thought
LangChain is a recent library that allows us to use the power of LLMs in different contexts.
I find it very useful to be able to use an LLM to understand the context, to understand what the user is requesting, and then to run my own custom function to actually solve the task.
This will allow you to write code that is versatile. To add a feature to your app all you need to do is write a function and tell the model to use this function when it thinks it is needed, and you are done!
If you were interested in this article follow me on Medium!
💼 Linkedin ️| 🐦 Twitter | 💻 Website
This article was published on Towards Data Science