← Writing
AI & Generative AI

Unpacking Tools and Chains with LangChain

Data Mastery Series — Episode 42: LangChain Website (Part 17)

30 Dec 202414 min readLangChainLangGraphAI AgentDashboard
LangChain Series · Part 4 of 19

Unpacking Tools and Chains with LangChain

Data Mastery Series — Episode 42: LangChain Website (Part 17)

Connect with me and follow our journey: Linkedin, Facebook


Welcome back to the Data Mastery Series! If you’ve been following along, you know we’ve built a comprehensive foundation, covering everything from LangChain basics to advanced applications. Here’s a quick recap of our journey so far:

Note:This post is inspired by insights from the official LangChain documentation and represents my practical learning journey.

In this episode, we’re shifting our focus to two critical components in the LangChain ecosystem: Tools and Chains. These elements are essential for building robust and versatile AI applications. We’ll explore what they are, how they work, and how you can use them effectively.

Tools: Interacting with the World

LangChain Tools are like the hands and feet of an agent, allowing it to interact with the outside world. They are interfaces that enable an agent, a chain, or even a standalone LLM to perform specific actions. Each tool bundles together several key pieces of information:

  • Name: A unique identifier.
  • Description: Explains the tool’s purpose and usage, essential for LLMs to understand when to use it.
  • JSON Schema: Defines the input structure the tool accepts.
  • Function: The executable code for the tool.
  • Return Direct: Indicates if the tool’s output should be directly returned to the user.

This structured definition allows LLMs to decide which tool to use for specific tasks and execute the corresponding action effectively.

Working with Default Tools

LangChain provides several built-in tools for common tasks. Here’s an example using WikipediaQueryRun to fetch information:

Example - Working with Default Tools

Initialize the tool

api_wrapper = WikipediaAPIWrapper(top_k_results=1, doc_content_chars_max=100)
tool = WikipediaQueryRun(api_wrapper=api_wrapper)

Get the default name, description and JSON schema of the tool

print("Tool name:", tool.name)
print("Tool description:", tool.description)
print("Tool arguments:", tool.args)
print("Tool return direct:", tool.return_direct)

Example to call tool

print('\n##########################')
print("Example to call tool with a dictionary:")
print(tool.run({"query": "langchain"}))

You can also call a tool with a single string if it only expects one argument

print('\n##########################')
print("Call a tool with a single string:")
print(tool.run("langchain"))

Output:

'''
Tool name: wikipedia
Tool description: A wrapper around Wikipedia. Useful for when you need to answer general questions about people, places, companies, facts, historical events, or other subjects. Input should be a search query.
Tool arguments: {'query': {'description': 'query to look up on wikipedia', 'title': 'Query', 'type': 'string'}}
Tool return direct: False

##########################
Example to call tool with a dictionary:
Page: LangChain
Summary: LangChain is a software framework that helps facilitate the integration of

##########################
Call a tool with a single string:
Page: LangChain
Summary: LangChain is a software framework that helps facilitate the integration of
'''

Defining Custom Tools

When creating your own agents, you’ll need to define the specific Tools that they can use. Tools include:

  • Name (str): A unique name for the tool.
  • Description (str): A description of what the tool does.
  • args_schema (Pydantic BaseModel): A schema for the tool’s arguments.

Here are three ways to define custom tools:

1. Using the @toolDecorator:
The simplest method. Automatically uses the function’s name and docstring for metadata.

Example - @tool Decorator

@tool
def search(query: str) -> str:
"""Look up things online."""
return "LangChain"

print("Tool name (search):",search.name)
print("Tool description (search):", search.description)
print("Tool arguments (search):", search.args)

output

'''
Tool name (search): search
Tool description (search): Look up things online.
Tool arguments (search): {'query': {'title': 'Query', 'type': 'string'}}
'''

@tool
def multiply(a: int, b: int) -> int:
"""Multiply two numbers."""
return a * b
print('\n##########################')
print("Tool name (multiply):", multiply.name)
print("Tool description (multiply):", multiply.description)
print("Tool arguments (multiply):", multiply.args)

output

'''
Tool name (multiply): multiply
Tool description (multiply): Multiply two numbers.
Tool arguments (multiply): {'a': {'title': 'A', 'type': 'integer'}, 'b': {'title': 'B', 'type': 'integer'}}
'''

Use a new name for the second search tool to avoid overwriting

class SearchInput(BaseModel):
query: str = Field(description="should be a search query")

@tool("search-tool", args_schema=SearchInput, return_direct=True)
def search(query: str) -> str:
"""Look up things online."""
return "LangChain"

print("Custom Tool name (search-tool):",search.name)
print("Custom Tool description (search-tool):", search.description)
print("Custom Tool arguments (search-tool):", search.args_schema.schema()["properties"])
print("Custom Tool return direct (search-tool):", search.return_direct)

output

'''
Custom Tool name (search-tool): search-tool
Custom Tool description (search-tool): Look up things online.
Custom Tool arguments (search-tool): {'query': {'title': 'Query', 'description': 'should be a search query', 'type': 'string'}}
Custom Tool return direct (search-tool): True
'''

2. Subclassing BaseTool:
This method gives you the most control over your tool’s definition but requires more code.

Example - @tool Decorator

class SearchInput(BaseModel):
query: str = Field(description="should be a search query")

class CalculatorInput(BaseModel):
a: int = Field(description="first number")
b: int = Field(description="second number")

class CustomSearchTool(BaseTool):
name: str = "custom_search"
description: str = "useful for when you need to answer questions about current events"
args_schema: Type[BaseModel] = SearchInput

def _run(  
    self, query: str, run_manager: Optional[CallbackManagerForToolRun] = None  
) -> str:  
    """Use the tool."""  
    return "LangChain"  

async def _arun(  
    self, query: str, run_manager: Optional[AsyncCallbackManagerForToolRun] = None  
) -> str:  
    """Use the tool asynchronously."""  
    raise NotImplementedError("custom_search does not support async")  

class CustomCalculatorTool(BaseTool):
name: str = "Calculator"
description: str = "useful for when you need to answer questions about math"
args_schema: Type[BaseModel] = CalculatorInput
return_direct: bool = True

def _run(  
    self, a: int, b: int, run_manager: Optional[CallbackManagerForToolRun] = None  
) -> str:  
    """Use the tool."""  
    return a * b  

async def _arun(  
    self,  
    a: int,  
    b: int,  
    run_manager: Optional[AsyncCallbackManagerForToolRun] = None,  
) -> str:  
    """Use the tool asynchronously."""  
    raise NotImplementedError("Calculator does not support async")  

search = CustomSearchTool()
print('\n##########################')
print("Custom Tool name (custom_search):", search.name)
print("Custom Tool description (custom_search):", search.description)
print("Custom Tool arguments (custom_search):", search.args_schema.schema()["properties"])

multiply = CustomCalculatorTool()
print('\n##########################')
print("Custom Tool name (Calculator):", multiply.name)
print("Custom Tool description (Calculator):", multiply.description)
print("Custom Tool arguments (Calculator):", multiply.args_schema.schema()["properties"])
print("Custom Tool return direct (Calculator):", multiply.return_direct)

output

'''
##########################
Custom Tool name (custom_search): custom_search
Custom Tool description (custom_search): useful for when you need to answer questions about current events
Custom Tool arguments (custom_search): {'query': {'title': 'Query', 'description': 'should be a search query', 'type': 'string'}}

##########################
Custom Tool name (Calculator): Calculator
Custom Tool description (Calculator): useful for when you need to answer questions about math
Custom Tool arguments (Calculator): {'a': {'title': 'A', 'description': 'first number', 'type': 'integer'}, 'b': {'title': 'B', 'description': 'second number', 'type': 'integer'}}
Custom Tool return direct (Calculator): True
'''

3. Using StructuredTool Dataclass:

A middle ground between the first two options.

Example - Using StructuredTool Dataclass (search_function)

def search_function(query: str):
return "LangChain"

search = StructuredTool.from_function(
func=search_function,
name="Search",
description="useful for when you need to answer questions about current events",
)

print("Custom Tool name (Search):", search.name)
print("Custom Tool description (Search):", search.description)
print("Custom Tool arguments (Search):", search.args)

output

'''
Custom Tool name (Search): Search
Custom Tool description (Search): useful for when you need to answer questions about current events
Custom Tool arguments (Search): {'query': {'title': 'Query', 'type': 'string'}}
'''

Example - Using StructuredTool Dataclass (CalculatorInput)

class CalculatorInput(BaseModel):
a: int = Field(description="first number")
b: int = Field(description="second number")

def multiply(a: int, b: int) -> int:
"""Multiply two numbers."""
return a * b

calculator = StructuredTool.from_function(
func=multiply,
name="Calculator",
description="multiply numbers",
args_schema=CalculatorInput,
return_direct=True,
)
print('\n##########################')
print("Custom Tool name (Calculator):", calculator.name)
print("Custom Tool description (Calculator):", calculator.description)
print("Custom Tool arguments (Calculator):", calculator.args_schema.schema()["properties"])

output

'''
Custom Tool name (Calculator): Calculator
Custom Tool description (Calculator): multiply numbers
Custom Tool arguments (Calculator): {'a': {'title': 'A', 'description': 'first number', 'type': 'integer'}, 'b': {'title': 'B', 'description': 'second number', 'type': 'integer'}}
'''

Handling Tool Errors

If a tool encounters an error, you can handle it gracefully to keep the agent running.

Example - Handling Tool Errors

def search_tool1(s: str):
raise ToolException("The search tool1 is not available.")

'''

When handle_tool_error is not set, it will error.

search = StructuredTool.from_function(
func=search_tool1,
name="Search_tool1",
description="A bad tool",
)

search.run("test") #This will error

'''

Define custom handler

def _handle_error(error: ToolException) -> str:
return (
"The following errors occurred during tool execution:"
+ error.args[0]
+ "Please try another tool."
)

search = StructuredTool.from_function(
func=search_tool1,
name="Search_tool1",
description="A bad tool",
handle_tool_error=_handle_error,
)
print(search.run("test"))

output

'''
The following errors occurred during tool execution:The search tool1 is not available.Please try another tool.
'''

Tools as OpenAI Functions

You can use LangChain tools as OpenAI functions, making it easy to use them with models that support function calling.

Example - Tools as OpenAI Functions

model = ChatOpenAI(model="gpt-3.5-turbo", api_key=OPENAI_API_KEY)

tools = [MoveFileTool()]
functions = [convert_to_openai_function(t) for t in tools]

print("OpenAI Function:", functions[0])

message = model.invoke(
[HumanMessage(content="move file foo to bar")], functions=functions
)
print('\n##########################')
print("OpenAI Function call message:", message)
print("OpenAI Function call:",message.additional_kwargs["function_call"])

model_with_functions = model.bind_functions(tools)
print('\n##########################')
print("Model with bind_functions:",model_with_functions.invoke([HumanMessage(content="move file foo to bar")]))

model_with_tools = model.bind_tools(tools)
print('\n##########################')
print("Model with bind_tools:",model_with_tools.invoke([HumanMessage(content="move file foo to bar")]))

output

'''
OpenAI Function: {'name': 'move_file', 'description': 'Move or rename a file from one location to another', 'parameters': {'properties': {'source_path': {'description': 'Path of the file to move', 'type': 'string'}, 'destination_path': {'description': 'New path for the moved file', 'type': 'string'}}, 'required': ['source_path', 'destination_path'], 'type': 'object'}}

##########################
OpenAI Function call message: content='' additional_kwargs={'function_call': {'arguments': '{"source_path":"foo","destination_path":"bar"}', 'name': 'move_file'}, 'refusal': None} response_metadata={'token_usage': {'completion_tokens': 21, 'prompt_tokens': 77, 'total_tokens': 98, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'function_call', 'logprobs': None} id='run-320c8e5f-fa8e-475c-9bb6-6bb8a31652b2-0' usage_metadata={'input_tokens': 77, 'output_tokens': 21, 'total_tokens': 98, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}
OpenAI Function call: {'arguments': '{"source_path":"foo","destination_path":"bar"}', 'name': 'move_file'}

##########################
Model with bind_functions: content='' additional_kwargs={'function_call': {'arguments': '{"source_path":"foo","destination_path":"bar"}', 'name': 'move_file'}, 'refusal': None} response_metadata={'token_usage': {'completion_tokens': 21, 'prompt_tokens': 77, 'total_tokens': 98, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'function_call', 'logprobs': None} id='run-b3e459b7-d6a9-400d-b8f5-81281fd473f9-0' usage_metadata={'input_tokens': 77, 'output_tokens': 21, 'total_tokens': 98, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}

##########################
Model with bind_tools: content='' additional_kwargs={'tool_calls': [{'id': 'call_WpFV2KqgRJOvJzuKb0NoYvj8', 'function': {'arguments': '{"source_path":"foo","destination_path":"bar"}', 'name': 'move_file'}, 'type': 'function'}], 'refusal': None} response_metadata={'token_usage': {'completion_tokens': 21, 'prompt_tokens': 77, 'total_tokens': 98, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-3.5-turbo-0125', 'system_fingerprint': None, 'finish_reason': 'tool_calls', 'logprobs': None} id='run-4f3f5722-3eb3-4801-88cd-4040158e8636-0' tool_calls=[{'name': 'move_file', 'args': {'source_path': 'foo', 'destination_path': 'bar'}, 'id': 'call_WpFV2KqgRJOvJzuKb0NoYvj8', 'type': 'tool_call'}] usage_metadata={'input_tokens': 77, 'output_tokens': 21, 'total_tokens': 98, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}
'''

Chains: Sequences of Actions

Chains in LangChain allow for sequential operations, combining tools, LLMs, or data-processing steps. Chains can be categorized into:

  1. LCEL Chains: Modern, flexible chains built using the LangChain Expression Language.
  2. Legacy Chains: Older chains subclassed from the Chain class.

Examples of LCEL Chain:

  1. **create_stuff_documents_chain**
    Combines multiple documents into a single prompt for an LLM to process.
    Use Case: Summarizing or analyzing batches of documents in one go.
  2. **create_sql_query_chain**
    Transforms natural language into SQL queries for database interaction.
    Use Case: Answering user questions by querying structured data.
  3. **create_openai_fn_runnable**
    Utilizes OpenAI function calling to produce structured output formats.
    Use Case: Enforcing specific output formats, like JSON or XML.
  4. **create_retrieval_chain**
    Fetches docs. from a retriever and generates a response based on them.
    Use Case: Q&A systems that require accessing external document repositories.

Examples of Legacy Chains:

  1. **APIChain**
    Dynamically constructs inputs for API calls using an LLM.
    Use Case: Automating interactions with APIs based on natural language inputs.
  2. **StuffDocumentsChain**
    Merges multiple documents into a single prompt for LLM processing.
    Use Case: Bulk summarization or document analysis.
  3. **ReduceDocumentsChain**
    Processes large datasets by reducing them into manageable chunks iteratively.
    Use Case: Handling data that exceeds an LLM’s context window.
  4. **RefineDocumentsChain**
    Builds answers iteratively by refining the response with additional context.
    Use Case: Producing detailed answers by integrating new information progressively.
  5. **LLMChain**
    A straightforward chain that formats prompts for an LLM.
    Use Case: Simple use cases like text completion or paraphrasing.
  6. **ConversationalRetrievalChain**
    Enables document retrieval and response generation during conversations.
    Use Case: Interactive Q&A with document collections.
  7. **LLMMath**
    Leverages an LLM for solving mathematical problems.
    Use Case: Solving and explaining math queries quickly.
  8. **MapReduceDocumentsChain**
    Applies an LLM to each document and combines results iteratively.
    Use Case: Tasks requiring document-level processing followed by summary aggregation.
  9. **create_extraction_chain_pydantic**
    Extracts structured information from text into a Pydantic model.
    Use Case: Data extraction for structured applications like JSON or database storage.
  10. **RetrievalQAWithSourcesChain**
    Retrieves relevant documents and provides answers with proper citations.
    Use Case: Research-based Q&A with traceable references.

Key Takeaways

In this episode, we explored the power of Tools and Chains:

  • Tools: Allow agents to interact with the external world, performing tasks such as retrieving information or executing calculations.
  • Chains: Enable sequences of actions for structured processing, enhancing the capability of LangChain applications.

In the next episode, we’ll delve into LangGraph, a powerful feature for building and orchestrating complex workflows within LangChain. Stay tuned! 🚀


Data Science Explore the world of data science with Donato_Story

Dashboard Discover the power of data visualization with Donato_Story

Donato_Journey Join me on my journey (Thai version)

Course_Review Discover the training courses with Donato_Story (Thai version)

Let’s Connect!

Your thoughts and feedback are invaluable. Feel free to share them in the comments or connect with me on

Originally published on Medium

Related