Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 24 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@

## langchain-crash-course

This repository contains a crash course on Langchain, a framework for developing applications using large language models (LLMs). The course covers various aspects of working with LLMs, including chat models, prompt templates, chains, retrieval augmented generation (RAG), agents, and tools. It also includes examples of using different LLM providers and Ollama models.

## Course Outline

1. Setup Environment
2. Chat Models
3. Prompt Templates
4. Chains
5. RAG
6. Agents & Tools

### Prerequisites

- Python 3.10 or 3.11
- Poetry

### 1_chat_models

- 1_chat_model_basic.py
Expand Down Expand Up @@ -38,10 +54,12 @@
- 8_rag_web_scrape_firecrawl.py
- rag_with_metadata.py

### Ollama Models

- llama3.2:3b Simple, quick tasks

- gemma3:4b Balanced performance
### 5_agents_tools

- deepseek-r1:14b Complex analysis and best quality
- agent_tools_basic.py
- agent_react_chat.py
- agent_react_rag_context.py
- rag.py
- tools/tool_basetool.py
- tools/tool_constructor.py
- tools/tool_decorator.py
2 changes: 1 addition & 1 deletion langchain-crash-course/5_agents_tools/agent_tools_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ async def main() -> None:
while True:
try:
# User Query
query: str = (await ainput("You: ")).strip()
query: str = (await ainput(prompt="You: ")).strip()

if not query:
continue
Expand Down
189 changes: 189 additions & 0 deletions langchain-crash-course/5_agents_tools/tools/tool_basetool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
"""
Demonstrates how to create and use LangChain tools by subclassing BaseTool.

This script defines two custom tools:
1. SimpleWebSearchTool: A tool that uses the Tavily API to perform a web search.
2. MultiplyNumbersTool: A simple tool to multiply two numbers.

It then creates a LangChain agent that can use these tools and runs an interactive
chat session where the user can interact with the agent. This approach provides
fine-grained control over the tool's implementation.
"""

# Import standard libraries
import asyncio
import os
import sys
from logging import Logger
from pathlib import Path
from typing import Any, Dict, Type

# Add parent directory to path
sys.path.append(str(Path(__file__).parent.parent))

# Import necessary libraries
from aioconsole import ainput
from dotenv import load_dotenv

# Import langchain modules
from langchain import hub
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.messages import AIMessage, HumanMessage
from langchain_core.messages.base import BaseMessage
from langchain_core.runnables import Runnable
from langchain_core.tools import BaseTool
from langchain_ollama import ChatOllama
from pydantic import BaseModel, Field
from tavily import TavilyClient

# Import custom logger
from util.logger import ReActAgentLogger

# Load environment variables
load_dotenv()

# Module path
module_path: Path = Path(__file__).resolve()

# Set logger
logger: Logger = ReActAgentLogger.get_logger(module_name=module_path.name)

# ==================== Define tools====================


class SimpleWebSearch(BaseModel):
"""Input model for the SimpleWebSearchTool, specifying the search query."""

query: str = Field(description="Search query")


class MultiplyNumbers(BaseModel):
"""Input model for the MultiplyNumbersTool, specifying the two numbers to multiply."""

num1: float = Field(description="First number")
num2: float = Field(description="Second number")


class SimpleWebSearchTool(BaseTool):
"""Tool for searching the web."""

name: str = "Simple_Web_Search"
description: str = "Useful for searching the web."
args_schema: Type[BaseModel] = SimpleWebSearch

def _run(self, query: str) -> str:
"""Executes the web search synchronously."""
try:
client = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))
results: Dict[str, Any] = client.search(query=query)
return f"Search results for: {query}\n\n{results}\n"
except Exception as e:
return f"An error occurred during the Tavily search: {e}"

async def _arun(self, query: str) -> str:
"""Executes the web search asynchronously."""
return await asyncio.to_thread(self._run, query)


class MultiplyNumbersTool(BaseTool):
"""Tool for multiplying two numbers."""

name: str = "Multiply_Numbers"
description: str = "Useful for multiplying two numbers."
args_schema: Type[BaseModel] = MultiplyNumbers

def _run(self, num1: float, num2: float) -> str:
"""Executes the multiplication synchronously."""
result: float = num1 * num2
return f"The product of {num1} and {num2} is {result}\n"


tools: list = [
SimpleWebSearchTool(), # Simple web search tool
MultiplyNumbersTool(), # Multiply numbers tool
]

# ==================== Create LMM and Agent ====================
# Create Chat Model
llm = ChatOllama(model="llama3.2:3b")

# pull prompt template from hub
prompt_template: Any = hub.pull(owner_repo_commit="hwchase17/openai-tools-agent")

# Create an agent
agent: Runnable[Any, Any] = create_tool_calling_agent(
llm=llm, # llm to use
tools=tools, # tools to use
prompt=prompt_template, # prompt to use
)

# Create agent executor
agent_executor: AgentExecutor = AgentExecutor.from_agent_and_tools(
agent=agent, # agent to use
tools=tools, # tools to use
verbose=True, # prints out the agent's thought process
handle_parsing_errors=True, # gracefully handles errors in parsing the agent output
)


# ==================== Run tools calling agent ====================
async def main() -> None:
"""
Runs the main asynchronous loop for the chat application.

This function initializes the agent, handles API key checks, and manages
the interactive chat session with the user, including history management
and graceful exit.
"""
# Check TAVILY_API_KEY
if not os.getenv("TAVILY_API_KEY"):
print(
"\n[ERROR] TAVILY_API_KEY not found in environment variables."
"\nPlease set the key in your .env file to use the web search tool."
)
sys.exit(1) # Exit the application with a non-zero status code

logger.info(msg="========= Start BaseTool Calling AI Agent Application ==========")
print("Type 'exit' to end the conversation.")

# Initialize chat history
chat_history: list[BaseMessage] = []

while True:
try:
query: str = (await ainput(prompt="You: ")).strip()

if not query:
print("Please ask a question!.")
continue

if query.lower() == "exit":
logger.info(msg="User exited conversation")
print("Exiting...")
break

# Process user query through agent executor
response: Any = await agent_executor.ainvoke(
input={"input": query, "chat_history": chat_history}
)

# Display AI response
if response:
logger.info(msg=f"AI: {response['output']:100}.....")
print(f"AI: {response['output']}")

# Update chat history
chat_history.append(HumanMessage(content=query))
chat_history.append(AIMessage(content=response["output"]))

except (KeyboardInterrupt, EOFError, asyncio.CancelledError):
logger.info(msg="Keyboard interrupt or EOF error")
print("Exiting...")
break

except Exception as e:
logger.error(msg=f"Unexpected error: {e}")


if __name__ == "__main__":
asyncio.run(main=main())
21 changes: 12 additions & 9 deletions langchain-crash-course/5_agents_tools/tools/tool_constructor.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ class ConcatenateStringsArgs(BaseModel):
description="Useful for reversing a string.",
),
StructuredTool.from_function(
func=concatenate_strings,
name="Concatenate Strings",
description="Useful for concatenating two strings.",
args_schema=ConcatenateStringsArgs,
func=concatenate_strings, # function to call
name="Concatenate Strings", # name of the tool
description="Useful for concatenating two strings.", # description of the tool
args_schema=ConcatenateStringsArgs, # args schema for the tool
),
]

Expand All @@ -75,21 +75,24 @@ class ConcatenateStringsArgs(BaseModel):

# ==================== Create agent====================
agent: Runnable[Any, Any] = create_tool_calling_agent(
llm=llm,
tools=tools,
prompt=prompt_template,
llm=llm, # llm to use
tools=tools, # tools to use
prompt=prompt_template, # prompt to use
)

# ==================== Create agent executor====================
agent_executor: AgentExecutor = AgentExecutor.from_agent_and_tools(
agent=agent, tools=tools, verbose=True, handle_parsing_errors=True
agent=agent, # agent to use
tools=tools, # tools to use
verbose=True, # prints out the agent's thought process
handle_parsing_errors=True, # gracefully handles errors in parsing the agent output
)


# ==================== Run tools calling agent ====================
async def main() -> None:
print(
"\nStart chatting with Tool Calling Agent AI! Type 'exit' to end the conversation."
"\nStart chatting with Constructor Tool Calling Agent AI! Type 'exit' to end the conversation."
)

# Initialize chat history
Expand Down
Loading