How to create LLM tools from any Python SDK using langchain-autotools
Providing LLMs with access to our internal and external APIs and databases is one of the most promising ways of using LLMs in the business context. While chat-bot-applications like ChatGPT are certainly great and interesting, the productivity gains of LLMs just start there - they are much much higher if we find ways to integrate these models into our existing workflows or create new workflows for our daily businesses.
The concept for how to enable LLMs to interact with other technologies is already quite established - it's called Tools.
While the concept of tools is indeed very good, it's not that straightforward to design these tools in a way that our LLMs can work with them. And, at the end of the day, it's a lot of work to create them.
That's where today's blog topic comes into play: langchain-autotools. It's a community-maintained library integrating with the well-known Langchain SDK. The idea behind langchain-autotools is to make the creation of tools for LLMs as easy as possible.
What are LLM tools?
Tools in the context of LLMs, in its purest form, are just functions of code that are run after an LLM decides to use them.
The whole process is - in general - rather simple:
-
Create a code runtime environment, where you can run code. A python environment for example.
-
Create a function that for example interacts with an API or a database. The function can also have parameters, they will be provided by the LLM later on.
-
Describe, in natural language, what this function does. Also describe the parameters and the return value.
-
In your application, when you want an LLM to execute a specific task, send your task prompt the LLM, as well as all the tool (think, function) descriptions. The LLM will then decide whether it can fulfill your prompt without using a tool - or if it needs a tool.
-
If the LLM decides to use a tool, it will tell you which tool to use. You then simply call your function with the parameters provided by the LLM. In pseudo python code, this is just an if statement:
To summarize: A tool is nothing more then a function with a good description so that the LLM knows what it does and how to use it.
If you're dealing with a lot of external system to engage with, you might already see the problem: It's quite cumbersome to wrap all your external systems into functions and provide each function with a good description.
Especially when we talk about using SDKs (and many external systems nowadays provide SDKs), they already have quite extensive documentation. Why can't we use this documentation to create tools for our LLMs?
How to create LLM tools with langchain-autotools
Well, the answer is: We can. And that's what langchain-autotools is for. It combines the Langchain Agent concept with said SDK documentation - for enabling these agents to use any SDK as their tool.
Note: An agent in this context is just an LLM that can use tools.
The process of creating a tool now gets tremendously simplified:
-
Instantiate a client object of the SDK you want to use.
-
Create an
autotool
, with the client you just created. -
Create an
agent
, using a predefined prompt and using thisautotool
. -
Execute the prompt with your agent.
In pseudo - code this looks like this:
As you can see the tedious part of wrapping your SDK functions into well- described functions is gone.
To explain the parameters of the AgentExecutor
:
agent
is the agent object you created, using an LLM as well as a prompt.prompt
: You can supply your own prompt, or use a predefined prompt. The Langchain prompt hub is an excellent place to find predefined prompts - and Langchain makes it easy to use them, by simply callinghub.pull("author/prompt-name")
.tools
: A list of tools to supply.autotool.operations
contains a list of all available functions of your SDK client.handle_parsing_errors
allows to define how the agent should react, when a tool can't be correctly invoked, as the LLM answered with an answer, which is not in the expected format - basically each time a runtime error after LLM response happens. By default, the execution will be aborted and an exception is raised. Setting this totrue
will send the error message along with the original task to the LLM again - and the execution is repeated. This is a surprisingly robust way of dealing with such errors. You can also provide your own error handling functions, read more about it in the Langchain documentation.max_iterations
is the maximum number of iterations the agent should try to execute the task. One iteration is one LLM call. So if you want the LLM to fetch your database user count, the first call will most likely result in a tool call. The second iteration will be you presenting the result to the agent, to decide whether the task is finished. This task would therefore require 2 iterations.
Specifying the functions to expose to the LLM
You might already object and note, that SDKs typically provide a lot of functions. Meaning, we would send all of them to the LLM which would probably overload the LLMs context window or at least invoke unnecessary costs. Most of the time, we'll only need two or three functions anyhow.
Langchain-autotools provides a way to filter the functions one wants to
expose to the LLM, by creating CrudControls
. These CrudControls
allow
to define CRUD verbs - only functions which match these verbs will
be exposed.
Note: As of time of this writing, langchain-autotools does not
automatically add ALL methods to the autotool.operations
list, just
the ones that have the following words in their name: get
, list
, read
.
We're not sure if this is a bug or intentional. However, we therefore
suggest to always manually define the CrudControls
, as this will override
the default behavior.
More specifically, one can define the following CRUD verbs:
create
read
update
delete
The CrudControls
interface allows to define
Let's see an example. Imagine we want to only expose the query
and queryMany
functions and the drop_database
function, we could define it like this:
Note that you specifically need to enable the CRUD verbs to use. Just providing a list of functions per verb is not enough.
The available parameters of CrudControls
are:
read
: Enable the read verbread_list
: Comma-separated list of function-names to expose to the LLMcreate
: Enable the create verbcreate_list
: Comma-separated list of function-names to expose to the LLMupdate
: Enable the update verbupdate_list
: Comma-separated list of function-names to expose to the LLMdelete
: Enable the delete verbdelete_list
: Comma-separated list of function-names to expose to the LLM
To use the created CrudControls
, simply pass it to the AutoToolWrapper
:
Example: How to create a tool to interact with Azure Blog Storage
Now that we know how to create tools using langchain-autotools, let's engage in a real world example. Let's say I have an Azure Blob Storage where I store some data required for some of my customer service requests.
We could create a small customer service LLM agent, which fetches documents from the Blob Storage if required and answers questions using these documents. In general, our agent therefore needs the following capabilities:
- Listing files in our blob storage
- Downloading files and extracting the content
We'll also use this as an example to demonstrate one of the limitations of the autotools library. But first things first, let's get started.
Lets start by installing the required libraries:
Get the connection string from your Azure Blob Storage account
Azure Blob Storage Connection String
Load the required dependencies and initialize the Azure Blob Storage client:
Next, we define the autotools client and the CRUD controls. We can find the correct function names of our Azure blob storage SDK in the documentation.
All we have to do is to initialize the autotools and Langchain as described in the chapter above:
But what is that? Most probably your agent didn't respond with the correct answer, even if the answer was provided in one of the blobs. Let's look at the output of the agent to find out why.
Well, it's pretty clear: The download_blob
function returns a StorageStreamDownloader
,
which in itself is not the content of the blob itself.
One would need to call download_blob('blobname').readall()
to get the
real text content. However, this is not possible as of now with langchain-autotools
.
One can only call top-level functions of the SDK. To circumvent this, we can
manually create an describe our own function, as follows:
Note the @tool
decorator, which makes our function a Langchain tool.
We can combine our automatically created autotool-functions with our own function as follows:
If we run our agent_executor once more, we'll get the correct answer, if it is provided in one of the blobs.
Further reading
- Extract structured data for LLMs from any website
- Chat with your confluence using LLMs
- How to fine-tune your very own LLM
Interested in how to train your very own Large Language Model?
We prepared a well-researched guide for how to use the latest advancements in Open Source technology to fine-tune your own LLM. This has many advantages like:
- Cost control
- Data privacy
- Excellent performance - adjusted specifically for your intended use