Sunday, October 12, 2025

Unlocking the Power of LLMs: Functions, Tools, and Agents with LangChain

Large Language Models (LLMs) have completely transformed the way humans interact with technology. Once limited to generating text for human consumption, these models are now stepping into a much more powerful role—they can interact directly with software systems, making decisions, retrieving information, and even taking actions autonomously.

Imagine asking an AI to fetch real-time weather data, calculate a financial projection, or pull specific entries from a database—all without writing traditional code for each request. This is the new frontier opened by function calling, a capability recently introduced by OpenAI. By enabling LLMs to output structured data such as JSON, developers can now allow AI to invoke functions or APIs intelligently, bridging the gap between natural language understanding and actionable software execution.


I. Introduction

Enter LangChain

This is where LangChain shines. LangChain is an open-source library designed to connect traditional software infrastructure with LLMs. Whether you want to integrate multiple language models, connect with vector databases, or build interactive agents, LangChain offers over 500 integrations to simplify the process.

Key features of LangChain include:

  • Support for multiple LLMs: Use different models in the same workflow without complex code changes.

  • Memory chains: Enable context-aware conversations or multi-step reasoning.

  • Agents and tools: Allow LLMs to decide which actions or tools to use dynamically.

What You Will Learn

This course focuses on the two major advancements that are redefining what developers can do with LLMs:

  1. Function Calling Made Simple
    Function calling empowers LLMs to invoke external programs as subroutines. This not only simplifies building tools for LLMs but also makes interactions more reliable and structured. For example, an LLM can now fetch, parse, and return data in JSON format without manual intervention.

  2. Tagging and Data Extraction
    Traditionally, LLMs struggled with structured or tabular data. With function calling integrated into LangChain, these models can now extract information efficiently. You’ll learn to tag data, parse entities, and handle structured outputs—skills critical for real-world AI applications.

Building a Conversational Agent

By combining function calling, tools, and LangChain agents, you’ll be able to create sophisticated conversational agents. These agents are capable of:

  • Understanding user queries.

  • Deciding which tool or function to invoke.

  • Maintaining context across multi-turn interactions.

This approach opens doors to building intelligent systems that do more than just chat—they actcompute, and retrieve information dynamically.

Final Thoughts

The integration of function calling with LangChain represents a paradigm shift in AI development. Instead of treating LLMs solely as text generators, we can now leverage them as intelligent orchestrators of software systems. By mastering these tools, you’ll not only understand the future of AI-powered applications but also gain hands-on experience building solutions that are smarter, faster, and more interactive.

-----------------------------------------------------------------------------------------------------------------------------------------------

II. Harnessing OpenAI Function Calling: A Beginner’s Guide

Large Language Models (LLMs) like OpenAI’s GPT series have transformed how we interact with computers, enabling human-like text generation. But what if these models could do more than just chat? What if they could call functions in your software, retrieve data, and act on it automatically? Enter OpenAI Function Calling—a game-changing feature recently added to the API.

What is OpenAI Function Calling?

Traditionally, LLMs generate text responses based on user input. Function calling changes the game by allowing the model to output structured data—usually JSON—indicating which function to call and with what arguments. This lets your AI interface directly with your software or external APIs, enabling actions like fetching real-time weather, querying databases, or triggering automated workflows.

How Does It Work?

OpenAI has fine-tuned some of its latest models to recognize when calling a function is relevant. You define your functions in a structured way, specifying:

  • Function name: The identifier used to call the function.

  • Description: Explains what the function does, helping the model decide when to use it.

  • Parameters: Defines what arguments the function requires, including types, descriptions, and optional enums (e.g., Celsius or Fahrenheit for temperature).

  • Required fields: Indicate which parameters must always be provided.

Once your function is defined, you pass it to the LLM through the functions parameter in the API call.

A Practical Example: Getting the Current Weather

Let’s consider a simple example: getCurrentWeather(location, unit). Since the LLM cannot access live weather data on its own, we provide this function.

  1. Define the function: Include a JSON object describing the function, parameters, and descriptions.

  2. Send messages to the LLM: Include user prompts along with the function definitions.

  3. Model decides whether to call the function:

    • By default, the model operates in auto mode, choosing whether to call the function.

    • You can also force the function call or prevent it entirely using the function_call parameter.

  4. Receive structured function call: The response includes the function name and a JSON dictionary of arguments.

⚠️ Important: The model doesn’t execute the function. It only tells you which function to call and with what arguments. You handle the actual execution in your code.

Handling Function Outputs

Once the function executes, you can pass its output back to the LLM to generate a final, natural language response. This enables conversational agents that not only understand queries but also act on them and explain the results in plain language. For example:

“The current weather in Boston is 72°F with a sunny and windy forecast.”

Tips and Best Practices

  • Token usage: Function definitions and descriptions count toward the model’s token limit, so keep them concise.

  • Parameter validation: Although the model is trained to return JSON, it isn’t strictly enforced. Always validate the output.

  • Function chaining: You can combine multiple function calls, passing outputs from one as inputs to another, creating more complex workflows.

What’s Next?

While you can use function calling directly through the OpenAI SDK, integrating it with LangChain primitives makes building multi-step, tool-using agents easier and faster. LangChain provides ready-made abstractions for chaining functions, routing queries, and maintaining conversational memory.


By leveraging OpenAI function calling, developers can turn LLMs from passive text generators into active participants in software workflows, unlocking a world of possibilities for automation, real-time data access, and intelligent agent design.

-----------------------------------------------------------------------------------------------------------------------------

II. Exploring OpenAI Function Calling with GPT-3.5-Turbo

Large Language Models (LLMs) like GPT have become remarkably adept at understanding and generating natural language. But their capabilities extend far beyond just chatting—they can now interact with software functions, enabling dynamic responses based on live data. This is made possible through OpenAI Function Calling, a feature that lets the model suggest which functions to call and with what arguments.

In this tutorial, we’ll explore how to implement OpenAI Function Calling using the OpenAI SDK, with practical Python examples.


Why Function Calling Matters

Traditionally, GPT models generate text for humans. But real-world applications often require more than just text—they need structured data, action triggers, or API calls.

Function Calling bridges this gap by allowing the LLM to output a JSON object specifying:

  • The function name it wants to call.

  • The arguments to pass to that function.

This approach enables intelligent software orchestration: the model decides what action is required, and your backend executes it.


Setting Up the Environment

Before getting started, ensure you have your OpenAI API key stored in a .env file:

python
import os import openai from dotenv import load_dotenv, find_dotenv _ = load_dotenv(find_dotenv()) openai.api_key = os.environ['OPENAI_API_KEY']

Defining a Function

For demonstration, we’ll use a dummy function get_current_weather that returns the current weather. In production, this could be replaced with a real API call:

python
import json def get_current_weather(location, unit="fahrenheit"): """Return the current weather in a given location""" weather_info = { "location": location, "temperature": "72", "unit": unit, "forecast": ["sunny", "windy"], } return json.dumps(weather_info)

Passing Functions to the LLM

We define the function metadata for OpenAI using a JSON-like structure:

python
functions = [ { "name": "get_current_weather", "description": "Get the current weather in a given location", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "The city and state, e.g., San Francisco, CA", }, "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}, }, "required": ["location"], }, } ]

The description and parameters help the model decide when and how to call this function.


Calling the Model

Here’s a basic example where the model decides whether to use the function:

python
messages = [ {"role": "user", "content": "What's the weather like in Boston?"} ] response = openai.ChatCompletion.create( model="gpt-3.5-turbo", messages=messages, functions=functions ) response_message = response["choices"][0]["message"] print(response_message["function_call"])

The output contains a function_call object with:

  • name: the function to call (get_current_weather)

  • arguments: a JSON blob containing the required inputs.

You can then convert the arguments to a Python dictionary:

python
args = json.loads(response_message["function_call"]["arguments"]) weather_data = get_current_weather(args) print(weather_data)

Handling Different Function Call Modes

OpenAI provides three modes for function calling:

  1. Auto (default): Model decides if a function call is necessary.

  2. None: Prevents the model from calling any function.

  3. Force call: Ensures the function is always called.

Example: Forcing a function call:

python
response = openai.ChatCompletion.create( model="gpt-3.5-turbo", messages=messages, functions=functions, function_call={"name": "get_current_weather"} )

Integrating Function Responses Back into the Model

After executing the function, you can pass the results back to the LLM to generate a final response:

python
messages.append( { "role": "function", "name": "get_current_weather", "content": weather_data, } ) final_response = openai.ChatCompletion.create( model="gpt-3.5-turbo", messages=messages, ) print(final_response["choices"][0]["message"]["content"])

Output:

"The current weather in Boston is 72°F with a sunny and windy forecast."


Tips and Best Practices

  • Token usage: Function definitions count toward your token limit. Keep descriptions concise.

  • Validation: The model suggests arguments but does not execute functions. Always validate before using.

  • Multi-step workflows: You can chain multiple function calls and feed outputs back into the model for complex logic.


Conclusion

OpenAI Function Calling transforms GPT from a passive text generator into an active decision-making agent that can interface with your applications. By combining function definitions, argument parsing, and model responses, you can create intelligent systems capable of real-world interactions.

Next, you can explore LangChain integration, which simplifies chaining multiple functions, routing queries, and maintaining conversation context for advanced applications.

-----------------------------------------------------------------------------------------------------------------------------

III. Unlocking the Power of LangChain Expression Language (LCEL)

When working with large language models (LLMs), building robust applications often requires composing multiple components—like prompt templates, models, retrievers, and parsers—into coherent pipelines. This is where LangChain Expression Language (LCEL) comes in, providing a new syntax and framework for constructing these pipelines more transparently and efficiently.


What is LCEL?

LCEL is a syntax and interface introduced in LangChain that standardizes how components—called runnables—are combined and executed. Runnables can be anything from a language model call to a prompt template or an output parser. By using LCEL, developers can define inputs, outputs, and execution logic clearly, with built-in support for streaming, batching, and asynchronous execution.


Key Features of LCEL

  1. Standardized Runnable Interface
    Every runnable exposes common methods:

    • invoke(): Executes the runnable on a single input.

    • stream(): Executes the runnable on a single input and streams the response.

    • batch(): Executes the runnable on a list of inputs.
      Each of these also has a corresponding asynchronous version.

  2. Input and Output Schemas
    Runnables define schemas for inputs and outputs, making pipelines predictable and debuggable.

  3. Fallback Mechanisms
    LLMs can be unpredictable. LCEL allows attaching fallbacks not only to individual components but also to entire chains, improving reliability.

  4. Parallel Execution
    LLM calls can take time. LCEL makes it easy to run components in parallel, reducing latency in complex pipelines.

  5. Built-in Logging
    As pipelines grow, tracking inputs, outputs, and steps becomes crucial. LCEL natively logs this information, helping with debugging and monitoring.


Creating a Simple LCEL Chain

Let’s build a basic pipeline that generates a short joke:

python
from langchain.prompts import PromptTemplate from langchain.llms import OpenAI from langchain.output_parsers import StrOutputParser # Create a prompt template prompt = PromptTemplate.from_template("Tell me a short joke") # Initialize the language model llm = OpenAI(temperature=0.7) # Initialize an output parser parser = StrOutputParser() # Chain: Prompt -> LLM -> Parser chain = prompt | llm | parser # Invoke the chain joke = chain.invoke({}) print(joke)

Example output:

"Why don't bears ever get caught in traffic? Because they always take the beariest best routes."


Building a Retrieval-Augmented Chain

LCEL also supports retrieval-augmented generation (RAG). You can fetch relevant documents, then pass them into a language model:

python
# Simple vector store with two texts vector_store = [ "Harrison worked at Kensho.", "Bears like to eat honey." ] # Runnable map to fetch relevant documents def retriever(question): return [text for text in vector_store if question.lower() in text.lower()] # Chain: Question -> Retriever -> Prompt -> LLM chain = { "context": lambda question: retriever(question), "question": lambda question: question } | prompt | llm result = chain.invoke({"question": "Where did Harrison work?"}) print(result)

This allows you to dynamically fetch context and feed it into your prompts.


Parameter Binding and Updating Components

LCEL makes it easy to bind or override parameters in a chain:

python
# Bind a new model or function to an existing runnable chain = chain.bind(model=OpenAI(temperature=0.0))

You can also attach fallbacks if a model fails to produce valid output:

python
from langchain.chains import ChainWithFallbacks fallback_chain = ChainWithFallbacks( main_chain=chain, fallback_chains=[older_model_chain] )

This ensures that your pipeline continues to work even when a component fails.


Advanced Execution Patterns

  • Batch Execution: Call the chain on multiple inputs simultaneously.

  • Streaming Responses: Get partial results as they are generated.

  • Asynchronous Execution: Run multiple chains in parallel to speed up processing.

LCEL pipelines can scale to hundreds of components, enabling sophisticated multi-step reasoning and RAG workflows.


Why LCEL is a Game Changer

  1. Transparency: Clear structure of inputs, outputs, and transformations.

  2. Flexibility: Easy to swap components, bind parameters, or attach fallbacks.

  3. Performance: Built-in support for parallelism and streaming.

  4. Observability: Native logging of each step in the chain.

LCEL empowers developers to build robust, complex LLM applications with minimal boilerplate and maximum clarity.


Next Steps

In the next lesson, LCEL is combined with OpenAI Function Calling, enabling pipelines that not only generate text but also decide when to call external functions, creating fully interactive and intelligent agents.

------------------------------------------------------------------------------------------------------------------------------------------------

III. Mastering LangChain Expression Language (LCEL) – Hands-On Tutorial

LangChain Expression Language (LCEL) is a modern syntax for building pipelines and chains with LLMs, making it easier to compose prompts, models, retrievers, and output parsers in a transparent and flexible way. Let’s walk through it step by step.


1. Setting Up the Environment

Before diving in, we set up the environment and initialize the OpenAI API:

python
import os import openai from dotenv import load_dotenv, find_dotenv _ = load_dotenv(find_dotenv()) openai.api_key = os.environ['OPENAI_API_KEY']

Tip: Always store your API key in .env to avoid hardcoding credentials.


2. Creating a Simple Chain

LCEL allows you to chain components together. Let’s start by creating a simple joke generator:

python
from langchain.prompts import ChatPromptTemplate from langchain.chat_models import ChatOpenAI from langchain.schema.output_parser import StrOutputParser prompt = ChatPromptTemplate.from_template("tell me a short joke about {topic}") model = ChatOpenAI() output_parser = StrOutputParser() chain = prompt | model | output_parser chain.invoke({"topic": "bears"})

Output example:

"Why don't bears ever get caught in traffic? Because they always take the beariest best routes."

This simple chain demonstrates prompt → model → parser flow.


3. Building a More Complex Chain with a Retriever

Often, LLMs need context from external data. LCEL supports retrieval-augmented pipelines:

python
from langchain.embeddings import OpenAIEmbeddings from langchain.vectorstores import DocArrayInMemorySearch # Create a simple vector store vectorstore = DocArrayInMemorySearch.from_texts( ["Harrison worked at Kensho", "Bears like to eat honey"], embedding=OpenAIEmbeddings() ) retriever = vectorstore.as_retriever() # Create a prompt template for retrieval-augmented answers template = """Answer the question based only on the following context: {context} Question: {question} """ prompt = ChatPromptTemplate.from_template(template)

Next, we use a RunnableMap to fetch context dynamically:

python
from langchain.schema.runnable import RunnableMap chain = RunnableMap({ "context": lambda x: retriever.get_relevant_documents(x["question"]), "question": lambda x: x["question"] }) | prompt | model | output_parser chain.invoke({"question": "where did Harrison work?"})

The pipeline now fetches relevant documents and feeds them into the LLM automatically.


4. Using OpenAI Function Calls

LCEL can integrate OpenAI Function Calling, allowing your model to decide when to call external functions:

python
functions = [ { "name": "weather_search", "description": "Search for weather given an airport code", "parameters": { "type": "object", "properties": { "airport_code": {"type": "string", "description": "The airport code to get the weather for"} }, "required": ["airport_code"] } } ] model = ChatOpenAI(temperature=0).bind(functions=functions) runnable = prompt | model runnable.invoke({"input": "what is the weather in SF"})

You can bind multiple functions and let the model pick which to call:

python
functions.append({ "name": "sports_search", "description": "Search for news of recent sport events", "parameters": { "type": "object", "properties": { "team_name": {"type": "string", "description": "The sports team to search for"} }, "required": ["team_name"] } }) model = model.bind(functions=functions) runnable = prompt | model runnable.invoke({"input": "how did the Patriots do yesterday?"})

5. Handling Fallbacks

LLMs aren’t perfect. LCEL allows you to attach fallbacks to chains or components:

python
from langchain.llms import OpenAI import json simple_model = OpenAI(temperature=0, max_tokens=1000, model="gpt-3.5-turbo-instruct") simple_chain = simple_model | json.loads challenge = "write three poems in a json blob, each with title, author, and first line" # If the simple_chain fails, fallback to a more robust chain model = ChatOpenAI(temperature=0) chain = model | StrOutputParser() | json.loads final_chain = simple_chain.with_fallbacks([chain]) final_chain.invoke(challenge)

Fallbacks ensure that even if the model fails to return valid JSON, the chain continues to work reliably.


6. Exploring the Runnable Interface

LCEL chains expose several powerful methods:

  1. invoke() → single synchronous input

  2. batch() → list of inputs, runs in parallel

  3. stream() → stream responses as they are generated

  4. ainvoke() → asynchronous invocation

Example:

python
# Batch processing chain.batch([{"topic": "bears"}, {"topic": "frogs"}]) # Streaming responses for t in chain.stream({"topic": "bears"}): print(t) # Async invocation response = await chain.ainvoke({"topic": "bears"}) print(response)

These methods give you flexibility and performance for complex LLM applications.


Conclusion

With LCEL, you can:

  • Compose simple to complex chains

  • Integrate retrieval-augmented workflows

  • Bind OpenAI functions and allow dynamic function calling

  • Add fallbacks for reliability

  • Handle batch, streaming, and async execution

LCEL makes it easier than ever to build robust, maintainable, and transparent LLM applications.

------------------------------------------------------------------------------------------------------------------------------------------------

IV. OpenAI Function Calling in LangChain with Pydantic

LangChain Expression Language (LCEL) supports OpenAI Function Calls, and Pydantic makes defining those functions easier.


1. What is Pydantic?

  • Pydantic is a Python data validation library.

  • It lets you define schemas with type hints and automatically validates data.

  • Pydantic objects can be exported to JSON, making it easy to generate OpenAI function definitions.

Normal Python class vs Pydantic model:

python
# Normal Python class class User: def __init__(self, name: str, age: int, email: str): self.name = name self.age = age self.email = email # Pydantic model from pydantic import BaseModel class UserModel(BaseModel): name: str age: int email: str

Pydantic validates types automatically. Passing age="bar" would fail with a Pydantic model but not in a normal class.


2. Converting Pydantic Models to OpenAI Functions

You can use Pydantic models to define OpenAI functions without manually writing the JSON schema.

python
from pydantic import BaseModel, Field from langchain.utils.openai_functions import convert_pydantic_to_openai_function # Define a Pydantic model for a function class WeatherSearch(BaseModel): """Search for weather given an airport code""" airport_code: str = Field(..., description="The airport code to get the weather for") # Convert Pydantic model to OpenAI function schema weather_function = convert_pydantic_to_openai_function(WeatherSearch)

Resulting JSON function schema:

json
{ "name": "weather_search", "description": "Search for weather given an airport code", "parameters": { "type": "object", "properties": { "airport_code": {"type": "string", "description": "The airport code to get the weather for"} }, "required": ["airport_code"] } }

The docstring becomes the function description, and Field descriptions map to parameter descriptions.


3. Using OpenAI Functions with LCEL

Once you have a function schema, you can pass it to a model and invoke it.

python
from langchain.chat_models import ChatOpenAI from langchain.prompts import ChatPromptTemplate model = ChatOpenAI(temperature=0) prompt = ChatPromptTemplate.from_messages([ ("system", "You are an assistant."), ("human", "{input}") ]) # Combine prompt and model runnable = prompt | model # Call the function runnable.invoke({"input": "what is the weather in SF"}, functions=[weather_function])

Output example:

json
{ "content": null, "additional_kwargs": { "function_call": { "name": "weather_search", "arguments": {"airport_code": "SFO"} } } }

4. Binding Functions to Models

You can bind functions to the model so you don’t need to pass them every time:

python
model_with_function = model.bind(functions=weather_function) # Invoke directly model_with_function.invoke({"input": "what is the weather in SF"})

Binding is useful when passing models around in chains or pipelines.


5. Using Multiple Functions

You can pass a list of functions, letting the LLM decide which function to call:

python
class ArtistSearch(BaseModel): """Get names of songs by a particular artist""" artist_name: str n: int = Field(..., description="Number of results to fetch") functions = [weather_function, convert_pydantic_to_openai_function(ArtistSearch)] model_with_functions = model.bind(functions=functions) # Example invocations model_with_functions.invoke({"input": "what is the weather in SF?"}) model_with_functions.invoke({"input": "get 5 songs by Coldplay"})

6. Summary

Using Pydantic + LCEL + OpenAI functions gives you:

  1. Type safety and validation of inputs.

  2. Automatic JSON function schemas from Python classes.

  3. Dynamic function calling in chains.

  4. Ability to bind functions to models for cleaner code.

This setup is ideal for structured extraction, tagging, and function orchestration in LangChain pipelines.

-----------------------------------------------------------------------------------------------------------------------------

IV. 1. Pydantic Recap

Pydantic allows you to create validated data models in Python, providing type checking and structured data:

python
from pydantic import BaseModel class pUser(BaseModel): name: str age: int email: str # Valid creation user = pUser(name="Jane", age=32, email="jane@gmail.com") print(user.name) # Jane # Invalid creation raises error user_invalid = pUser(name="Jane", age="bar", email="jane@gmail.com") # ❌ Raises ValidationError
  • Nested structures are supported:

python
from typing import List class Class(BaseModel): students: List[pUser] obj = Class(students=[pUser(name="Jane", age=32, email="jane@gmail.com")]) print(obj)

2. Pydantic → OpenAI Function

  • Use Pydantic models to define OpenAI function parameters.

  • Add docstrings for function descriptions.

  • Use Field for argument descriptions.

python
from pydantic import BaseModel, Field from langchain.utils.openai_functions import convert_pydantic_to_openai_function class WeatherSearch(BaseModel): """Call this with an airport code to get the weather at that airport""" airport_code: str = Field(description="airport code to get weather for") weather_function = convert_pydantic_to_openai_function(WeatherSearch) print(weather_function)
  • Important: Pydantic model must have a docstring for LangChain to generate a function; otherwise, it will fail:

python
class WeatherSearch1(BaseModel): airport_code: str convert_pydantic_to_openai_function(WeatherSearch1) # ❌ Error

3. Using Functions with ChatOpenAI

  • You can invoke a model with functions:

python
from langchain.chat_models import ChatOpenAI model = ChatOpenAI() model.invoke("what is the weather in SF today?", functions=[weather_function])
  • Bind a function to a model for repeated use:

python
model_with_function = model.bind(functions=[weather_function]) model_with_function.invoke("what is the weather in sf?")
  • Force a model to always call a specific function:

python
model_with_forced_function = model.bind( functions=[weather_function], function_call={"name":"WeatherSearch"} ) model_with_forced_function.invoke("what is the weather in sf?") model_with_forced_function.invoke("hi!") # still calls WeatherSearch

4. Using Functions in a Chain

python
from langchain.prompts import ChatPromptTemplate prompt = ChatPromptTemplate.from_messages([ ("system", "You are a helpful assistant"), ("user", "{input}") ]) chain = prompt | model_with_function chain.invoke({"input": "what is the weather in sf?"})
  • This allows you to treat a function-bound model like a normal LCEL chain component.


5. Multiple Functions (Dynamic Selection)

  • Let the LLM choose which function to call based on input:

python
class ArtistSearch(BaseModel): """Call this to get the names of songs by a particular artist""" artist_name: str = Field(description="name of artist to look up") n: int = Field(description="number of results") functions = [ convert_pydantic_to_openai_function(WeatherSearch), convert_pydantic_to_openai_function(ArtistSearch), ] model_with_functions = model.bind(functions=functions) # Examples model_with_functions.invoke("what is the weather in sf?") model_with_functions.invoke("what are three songs by taylor swift?") model_with_functions.invoke("hi!") # LLM decides no function is needed

✅ Key Takeaways

  1. Pydantic models = structured function arguments with validation.

  2. Docstrings are mandatory to describe the function for OpenAI.

  3. convert_pydantic_to_openai_function transforms models into OpenAI-compatible JSON functions.

  4. Binding functions to models simplifies reuse in chains and LCEL pipelines.

  5. Multiple functions let the model dynamically choose which function to call.

------------------------------------------------------------------------------------------------------------------------------------------------V. Tagging and Extraction using OpenAI functions in LangChain

1. Concept Overview

  • Tagging:

    • Input: unstructured text + structured description (schema)

    • Goal: LLM outputs a single structured object based on the schema.

    • Example: Extract sentiment (posnegneutral) and language (ISO 639-1) from text.

  • Extraction:

    • Input: unstructured text + structured description

    • Goal: LLM extracts multiple items/entities into a structured format.

    • Example: Extract a list of people mentioned in a text or list of papers cited in an article.


2. Setting Up Tagging

  1. Define Pydantic model for tagging:

python
from pydantic import BaseModel, Field class Tagging(BaseModel): """Tag a piece of text with particular info""" sentiment: str = Field(description="Sentiment of the text (pos, neg, neutral)") language: str = Field(description="Language of the text (ISO 639-1 code)")
  1. Convert Pydantic model to OpenAI function:

python
from langchain.utils.openai_functions import convert_pydantic_to_openai_function tagging_function = convert_pydantic_to_openai_function(Tagging)
  1. Set up model and chain:

python
from langchain.chat_models import ChatOpenAI from langchain.prompts import ChatPromptTemplate model = ChatOpenAI(temperature=0) # deterministic output model_with_functions = model.bind(functions=[tagging_function]) prompt = ChatPromptTemplate.from_messages([ ("system", "You are a helpful assistant"), ("user", "{input}") ]) tagging_chain = prompt | model_with_functions
  1. Invoke tagging chain:

python
tagging_chain.invoke({"input": "I love this product!"}) # Example output: {'sentiment': 'pos', 'language': 'en'}
  1. Use JSON output parser for convenience:

python
from langchain.output_parsers.openai_functions import JsonOutputFunctionsParser parser = JsonOutputFunctionsParser() parsed_output = parser.parse(model_output)

3. Setting Up Extraction

  1. Define schemas for extraction:

python
class Person(BaseModel): name: str age: int = None class Info(BaseModel): people: list[Person]
  1. Convert to OpenAI function:

python
extraction_function = convert_pydantic_to_openai_function(Info) model_with_extraction = model.bind( functions=[extraction_function], function_call={"name": "Info"} # force function )
  1. Optional: Prompt to improve reasoning (e.g., don’t guess missing values):

python
prompt = ChatPromptTemplate.from_messages([ ("system", "Extract relevant information; do not guess missing info."), ("user", "{input}") ])
  1. Extract entities:

python
extraction_chain = prompt | model_with_extraction result = extraction_chain.invoke({"input": "His mom is Martha."}) # Output: {'people': [{'name': 'Martha'}]}
  1. Use JSON key parser to extract specific field:

python
from langchain.output_parsers.openai_functions import JsonKeyOutputFunctionsParser key_parser = JsonKeyOutputFunctionsParser(key="people") parsed_people = key_parser.parse(result)

4. Real-World Example: Articles & Papers

  1. Load article:

python
from langchain.document_loaders import WebBaseLoader loader = WebBaseLoader("https://blog.example.com/llm-autonomous-agents") docs = loader.load() text = docs[0].page_content[:10000] # first 10k chars
  1. Define tagging for overview:

python
class Overview(BaseModel): summary: str language: str keywords: list[str] overview_function = convert_pydantic_to_openai_function(Overview)
  1. Define extraction for papers:

python
class Paper(BaseModel): title: str author: str = None class Info(BaseModel): papers: list[Paper] info_function = convert_pydantic_to_openai_function(Info)
  1. Chain with text splitting (for long articles):

python
from langchain.text_splitter import RecursiveCharacterTextSplitter splitter = RecursiveCharacterTextSplitter() splits = splitter.split_text(text) # Preprocess splits for chain def prepare_input(x): return [{"input": chunk} for chunk in x] prepped_splits = prepare_input(splits) # Map extraction chain over splits and flatten results def flatten(list_of_lists): return [item for sublist in list_of_lists for item in sublist]
  1. Invoke extraction chain on all splits:

python
all_results = flatten([extraction_chain.invoke(chunk) for chunk in prepped_splits])
  1. System prompt for accuracy:

  • "Extract only papers mentioned in the article. Do not guess or include the article itself. Return empty list if none."


✅ Key Takeaways

  • Tagging vs Extraction:

    • Tagging → structured object from text

    • Extraction → list of objects/entities from text

  • Pydantic models define the schema for the LLM to return.

  • Function binding allows deterministic, reusable function calls.

  • JSON output parsers simplify handling the structured data returned.

  • Text splitting + chaining handles long documents efficiently.

1. Concept Overview

  • Tagging:

    • Input: unstructured text + structured description (schema)

    • Goal: LLM outputs a single structured object based on the schema.

    • Example: Extract sentiment (posnegneutral) and language (ISO 639-1) from text.

  • Extraction:

    • Input: unstructured text + structured description

    • Goal: LLM extracts multiple items/entities into a structured format.

    • Example: Extract a list of people mentioned in a text or list of papers cited in an article.


2. Setting Up Tagging

  1. Define Pydantic model for tagging:

python
from pydantic import BaseModel, Field class Tagging(BaseModel): """Tag a piece of text with particular info""" sentiment: str = Field(description="Sentiment of the text (pos, neg, neutral)") language: str = Field(description="Language of the text (ISO 639-1 code)")
  1. Convert Pydantic model to OpenAI function:

python
from langchain.utils.openai_functions import convert_pydantic_to_openai_function tagging_function = convert_pydantic_to_openai_function(Tagging)
  1. Set up model and chain:

python
from langchain.chat_models import ChatOpenAI from langchain.prompts import ChatPromptTemplate model = ChatOpenAI(temperature=0) # deterministic output model_with_functions = model.bind(functions=[tagging_function]) prompt = ChatPromptTemplate.from_messages([ ("system", "You are a helpful assistant"), ("user", "{input}") ]) tagging_chain = prompt | model_with_functions
  1. Invoke tagging chain:

python
tagging_chain.invoke({"input": "I love this product!"}) # Example output: {'sentiment': 'pos', 'language': 'en'}
  1. Use JSON output parser for convenience:

python
from langchain.output_parsers.openai_functions import JsonOutputFunctionsParser parser = JsonOutputFunctionsParser() parsed_output = parser.parse(model_output)

3. Setting Up Extraction

  1. Define schemas for extraction:

python
class Person(BaseModel): name: str age: int = None class Info(BaseModel): people: list[Person]
  1. Convert to OpenAI function:

python
extraction_function = convert_pydantic_to_openai_function(Info) model_with_extraction = model.bind( functions=[extraction_function], function_call={"name": "Info"} # force function )
  1. Optional: Prompt to improve reasoning (e.g., don’t guess missing values):

python
prompt = ChatPromptTemplate.from_messages([ ("system", "Extract relevant information; do not guess missing info."), ("user", "{input}") ])
  1. Extract entities:

python
extraction_chain = prompt | model_with_extraction result = extraction_chain.invoke({"input": "His mom is Martha."}) # Output: {'people': [{'name': 'Martha'}]}
  1. Use JSON key parser to extract specific field:

python
from langchain.output_parsers.openai_functions import JsonKeyOutputFunctionsParser key_parser = JsonKeyOutputFunctionsParser(key="people") parsed_people = key_parser.parse(result)

4. Real-World Example: Articles & Papers

  1. Load article:

python
from langchain.document_loaders import WebBaseLoader loader = WebBaseLoader("https://blog.example.com/llm-autonomous-agents") docs = loader.load() text = docs[0].page_content[:10000] # first 10k chars
  1. Define tagging for overview:

python
class Overview(BaseModel): summary: str language: str keywords: list[str] overview_function = convert_pydantic_to_openai_function(Overview)
  1. Define extraction for papers:

python
class Paper(BaseModel): title: str author: str = None class Info(BaseModel): papers: list[Paper] info_function = convert_pydantic_to_openai_function(Info)
  1. Chain with text splitting (for long articles):

python
from langchain.text_splitter import RecursiveCharacterTextSplitter splitter = RecursiveCharacterTextSplitter() splits = splitter.split_text(text) # Preprocess splits for chain def prepare_input(x): return [{"input": chunk} for chunk in x] prepped_splits = prepare_input(splits) # Map extraction chain over splits and flatten results def flatten(list_of_lists): return [item for sublist in list_of_lists for item in sublist]
  1. Invoke extraction chain on all splits:

python
all_results = flatten([extraction_chain.invoke(chunk) for chunk in prepped_splits])
  1. System prompt for accuracy:

  • "Extract only papers mentioned in the article. Do not guess or include the article itself. Return empty list if none."


✅ Key Takeaways

  • Tagging vs Extraction:

    • Tagging → structured object from text

    • Extraction → list of objects/entities from text

  • Pydantic models define the schema for the LLM to return.

  • Function binding allows deterministic, reusable function calls.

  • JSON output parsers simplify handling the structured data returned.

  • Text splitting + chaining handles long documents efficiently.

------------------------------------------------------------------------------------------------------------------------------------------------

V. 1. Tagging

  • Schema Definition: Using Pydantic models to define the structure of the tags.

python
class Tagging(BaseModel): sentiment: str = Field(description="sentiment of text, should be `pos`, `neg`, or `neutral`") language: str = Field(description="language of text (should be ISO 639-1 code)")
  • Function Conversion: Convert Pydantic model to OpenAI function.

python
tagging_functions = [convert_pydantic_to_openai_function(Tagging)]
  • Chain Setup: Combine prompt → model bound with functions → output parser.

python
model_with_functions = model.bind(functions=tagging_functions, function_call={"name": "Tagging"}) tagging_chain = prompt | model_with_functions | JsonOutputFunctionsParser()
  • Invoke:

python
tagging_chain.invoke({"input": "I love langchain"}) tagging_chain.invoke({"input": "non mi piace questo cibo"})

2. Extraction

  • Schemas:

python
class Person(BaseModel): name: str age: Optional[int] class Information(BaseModel): people: List[Person]
  • Function Conversion & Binding:

python
extraction_functions = [convert_pydantic_to_openai_function(Information)] extraction_model = model.bind(functions=extraction_functions, function_call={"name": "Information"})
  • Prompt & Chain:

python
prompt = ChatPromptTemplate.from_messages([ ("system", "Extract the relevant information, if not explicitly provided do not guess. Extract partial info"), ("human", "{input}") ]) extraction_chain = prompt | extraction_model | JsonKeyOutputFunctionsParser(key_name="people")
  • Invoke:

python
extraction_chain.invoke({"input": "Joe is 30, his mom is Martha"})

3. Real-World Example: Article Tagging & Extraction

  • Load Article:

python
from langchain.document_loaders import WebBaseLoader loader = WebBaseLoader("https://lilianweng.github.io/posts/2023-06-23-agent/") documents = loader.load() doc = documents[0] page_content = doc.page_content[:10000]
  • Overview Tagging Schema:

python
class Overview(BaseModel): summary: str language: str keywords: str
  • Paper Extraction Schema:

python
class Paper(BaseModel): title: str author: Optional[str] class Info(BaseModel): papers: List[Paper]
  • Set Up Chains:

python
overview_tagging_function = [convert_pydantic_to_openai_function(Overview)] tagging_model = model.bind(functions=overview_tagging_function, function_call={"name":"Overview"}) tagging_chain = prompt | tagging_model | JsonOutputFunctionsParser()
python
paper_extraction_function = [convert_pydantic_to_openai_function(Info)] extraction_model = model.bind(functions=paper_extraction_function, function_call={"name":"Info"}) extraction_chain = prompt | extraction_model | JsonKeyOutputFunctionsParser(key_name="papers")
  • System Prompt for Accurate Extraction:

python
template = """A article will be passed to you. Extract from it all papers that are mentioned by this article follow by its author. Do not extract the name of the article itself. If no papers are mentioned that's fine - you don't need to extract any! Just return an empty list. Do not make up or guess ANY extra information. Only extract what exactly is in the text.""" prompt = ChatPromptTemplate.from_messages([ ("system", template), ("human", "{input}") ]) extraction_chain = prompt | extraction_model | JsonKeyOutputFunctionsParser(key_name="papers")

4. Handling Large Texts

  • Text Splitting:

python
from langchain.text_splitter import RecursiveCharacterTextSplitter text_splitter = RecursiveCharacterTextSplitter(chunk_overlap=0) splits = text_splitter.split_text(doc.page_content)
  • Flatten Function:

python
def flatten(matrix): return [item for row in matrix for item in row]
  • Runnable Lambda for Preprocessing:

python
from langchain.schema.runnable import RunnableLambda prep = RunnableLambda(lambda x: [{"input": doc} for doc in text_splitter.split_text(x)])
  • Chain Mapping & Flattening:

python
chain = prep | extraction_chain.map() | flatten chain.invoke(doc.page_content)

✅ Key Points

  1. Tagging → single structured object.

  2. Extraction → multiple structured objects.

  3. JSON parsers simplify access to nested results.

  4. For long texts → split → map extraction chain → flatten results.

  5. Always provide a clear system prompt to avoid LLM hallucinations.

------------------------------------------------------------------------------------------------------------------------------------------------

VI. LangChain tools and routing

1. Concept of a Tool

  • Tools in LangChain are Python functions that a language model can choose to call.

  • Two components:

    1. LLM decides which tool to use and with what inputs.

    2. The tool is actually invoked with those inputs.

  • LangChain comes with built-in tools: search, math, SQL, etc., but creating custom tools is often necessary.


2. Creating a Custom Tool

  • Define input schema using Pydantic to make it explicit for the LLM:

python
from pydantic import BaseModel, Field from langchain.tools import tool class WeatherInput(BaseModel): latitude: float = Field(description="Latitude of the location") longitude: float = Field(description="Longitude of the location")
  • Define the function and decorate it:

python
@tool(arg_schema=WeatherInput) def get_current_temperature(latitude: float, longitude: float) -> str: # Call external API (e.g., OpenMedio) and return temperature return f"Current temperature is {temp}°C"
  • Check args and description:

python
get_current_temperature.args # shows structured description
  • Convert to OpenAI function definition:

python
from langchain.tools.render import format_tool_to_openai_function openai_func_def = format_tool_to_openai_function(get_current_temperature)

3. Example of Another Tool

  • Wikipedia search tool:

python
@tool def search_wikipedia(query: str) -> str: pages = wikipedia.search(query) summaries = [wikipedia.page(p).summary for p in pages[:3]] return "\n".join(summaries)
  • Can also convert to OpenAI function definition similarly.


4. Using OpenAPI Specs

  • APIs often have OpenAPI specs.

  • LangChain can convert OpenAPI specs to OpenAI function definitions and callables:

python
from langchain.tools.openapi import openapi_spec_to_openai_function, OpenAPISpec spec = OpenAPISpec.from_text(openapi_json) functions, callables = openapi_spec_to_openai_function(spec)
  • This gives a list of function definitions usable by the LLM and actual callables for execution.


5. Routing: Choosing Which Tool to Call

  • Bind multiple tools to an LLM:

python
model = ChatOpenAI(temperature=0) functions = [format_tool_to_openai_function(get_current_temperature), format_tool_to_openai_function(search_wikipedia)] model_with_functions = model.bind(functions=functions)
  • LLM decides which function to call based on input:

python
model_with_functions.invoke("What is the weather in SF?") model_with_functions.invoke("Tell me about LangChain")
  • Can add a prompt to customize LLM behavior:

python
prompt = ChatPromptTemplate.from_messages([ ("system", "You are a helpful but sassy assistant"), ])

6. Output Handling

  • Two main types of outputs:

    1. Agent Action: LLM decides to call a tool.

    2. Agent Finish: LLM returns a normal response.

  • Use OpenAI Functions Agent Output Parser to parse outputs into:

    • result.tool → which tool to call

    • result.toolInput → dictionary of arguments

    • result.returnValues → LLM output when no tool is called


7. Routing Function

  • Defines what to do after LLM output:

python
def route(result): if isinstance(result, AgentFinish): return result.returnValues elif isinstance(result, AgentAction): tool_to_run = tool_lookup[result.tool] return tool_to_run(**result.toolInput)
  • Allows automatic execution of the selected tool.


8. Workflow Summary

  1. Define tools (custom or from OpenAPI).

  2. Convert tools to OpenAI function definitions.

  3. Bind multiple tools to an LLM.

  4. LLM receives user input → decides tool or response.

  5. Parse LLM output using output parser.

  6. Route the result → execute tool if needed.

  7. Return final output.


9. Example Use

python
chain.invoke("What is the weather in San Francisco right now?") # → Returns current temperature chain.invoke("What is LangChain?") # → Calls Wikipedia tool and returns summaries chain.invoke("Hi") # → Returns LLM message: "Hello, how can I assist you today?"

This is the core of LangChain routing: using the LLM to choose tools intelligently and execute them while handling outputs cleanly.


VI. 1. Setting Up Tools

A. Basic Search Tool

python
@tool def search(query: str) -> str: """Search for weather online""" return "42f"
  • @tool decorator wraps the function so it can be used by the LLM.

  • You can access its metadata:

    python
    search.name search.description search.args

B. Search Tool with Pydantic Input

python
class SearchInput(BaseModel): query: str = Field(description="Thing to search for") @tool(args_schema=SearchInput) def search(query: str) -> str: """Search for the weather online.""" return "42f"
  • Pydantic schema gives the LLM a structured input description.


C. Weather Tool (Open-Meteo API)

python
class OpenMeteoInput(BaseModel): latitude: float = Field(..., description="Latitude of the location") longitude: float = Field(..., description="Longitude of the location") @tool(args_schema=OpenMeteoInput) def get_current_temperature(latitude: float, longitude: float) -> dict: """Fetch current temperature for given coordinates.""" # Calls Open-Meteo API and returns closest hourly temperature
  • Decorated as a tool.

  • Converts to OpenAI function definition with format_tool_to_openai_function(get_current_temperature).

  • Run it with:

python
get_current_temperature({"latitude": 13, "longitude": 14})

D. Wikipedia Tool

python
@tool def search_wikipedia(query: str) -> str: """Run Wikipedia search and get page summaries.""" page_titles = wikipedia.search(query) summaries = [wikipedia.page(title=t).summary for t in page_titles[:3]] return "\n\n".join(summaries)
  • Returns top 3 Wikipedia page summaries.

  • Also can be converted to OpenAI function format.


2. OpenAPI Spec to OpenAI Functions

  • Load OpenAPI JSON spec:

python
spec = OpenAPISpec.from_text(text) pet_openai_functions, pet_callables = openapi_spec_to_openai_fn(spec)
  • Bind functions to a model:

python
model = ChatOpenAI(temperature=0).bind(functions=pet_openai_functions) model.invoke("what are three pets names")
  • Automatically calls correct function based on input.


3. Routing Between Tools

  • Bind your custom tools:

python
functions = [ format_tool_to_openai_function(f) for f in [search_wikipedia, get_current_temperature] ] model = ChatOpenAI(temperature=0).bind(functions=functions)
  • LLM selects tool based on input:

python
model.invoke("what is the weather in sf right now") model.invoke("what is langchain")

4. Prompt + Routing Chain

  • Prompt defines assistant behavior:

python
prompt = ChatPromptTemplate.from_messages([ ("system", "You are helpful but sassy assistant"), ("user", "{input}"), ]) chain = prompt | model
  • Output parser interprets LLM decisions:

python
from langchain.agents.output_parsers import OpenAIFunctionsAgentOutputParser chain = prompt | model | OpenAIFunctionsAgentOutputParser() result = chain.invoke({"input": "what is the weather in sf right now"})
  • result can be:

    • AgentAction → tool to call + input

    • AgentFinish → regular response


5. Route Function

python
from langchain.schema.agent import AgentFinish def route(result): if isinstance(result, AgentFinish): return result.return_values['output'] else: tools = { "search_wikipedia": search_wikipedia, "get_current_temperature": get_current_temperature, } return tools[result.tool].run(result.tool_input)
  • Automatically executes the chosen tool or returns a normal LLM response.


6. Final Chain

python
chain = prompt | model | OpenAIFunctionsAgentOutputParser() | route chain.invoke({"input": "What is the weather in san francisco right now?"}) chain.invoke({"input": "What is langchain?"}) chain.invoke({"input": "hi!"})
  • Returns actual weather, Wikipedia summaries, or simple greetings.


✅ Key Takeaways:

  1. @tool + Pydantic schema → structured, callable functions for LLM.

  2. OpenAPI spec → automatically generate functions + callables.

  3. Output parser → detects whether LLM wants to call a tool or just respond.

  4. Route function → executes tool or returns LLM output.

  5. Full workflow allows dynamic tool selection + execution based on LLM input.

------------------------------------------------------------------------------------------------------------------------------------------------

VII. Building a Conversational Agent with LangChain

1. Basics of an Agent

  • Agents = Language Model + Code (tools).

  • LM role: Decide which steps/tools to take and provide input arguments.

  • Agent loop:

    1. LM decides which tool to call.

    2. Tool executes and returns observation.

    3. Observation fed back to LM.

    4. Repeat until stopping criteria.

  • Stopping criteria examples:

    • LM decides to stop (AgentFinish)

    • Maximum iterations

    • Custom rules


2. Tools

You can define multiple tools to expand agent capabilities:

Example Tools

python
from langchain.agents import tool from pydantic import BaseModel, Field @tool def search_wikipedia(query: str) -> str: """Get top 3 Wikipedia summaries.""" import wikipedia pages = wikipedia.search(query)[:3] summaries = [wikipedia.page(title=p).summary for p in pages] return "\n\n".join(summaries) class WeatherInput(BaseModel): latitude: float longitude: float @tool(args_schema=WeatherInput) def get_current_temperature(latitude: float, longitude: float) -> str: import requests import datetime BASE_URL = "https://api.open-meteo.com/v1/forecast" params = {'latitude': latitude, 'longitude': longitude, 'hourly': 'temperature_2m', 'forecast_days': 1} response = requests.get(BASE_URL, params=params) results = response.json() time_list = [datetime.datetime.fromisoformat(t.replace('Z', '+00:00')) for t in results['hourly']['time']] temp_list = results['hourly']['temperature_2m'] closest = min(range(len(time_list)), key=lambda i: abs(time_list[i] - datetime.datetime.utcnow())) return f"The current temperature is {temp_list[closest]}°C"
  • Tools can be wrapped with format_tool_to_openai_function() to make them OpenAI function-compatible.

  • Additional tools can be added, e.g., a simple string reversal:

python
@tool def reverse_string(query: str) -> str: return query[::-1]

3. Prompt Template with Agent Scratchpad

  • Include placeholders for:

    • messages (chat history)

    • agent_scratchpad (intermediate tool outputs)

python
from langchain.prompts import ChatPromptTemplate prompt = ChatPromptTemplate.from_messages([ ("system", "You are a helpful but sassy assistant."), ("user", "{input}"), ("scratchpad", "{agent_scratchpad}") # dynamic placeholder ])

4. Running the Agent (Loop)

  • Create a loop that:

    1. Calls the LM with input + scratchpad.

    2. Gets tool + tool_input.

    3. Executes the tool.

    4. Updates scratchpad with observations.

    5. Repeat until stopping criteria.

python
from langchain.agents.output_parsers import OpenAIFunctionsAgentOutputParser from langchain.chat_models import ChatOpenAI from langchain.tools.render import format_tool_to_openai_function functions = [format_tool_to_openai_function(f) for f in [search_wikipedia, get_current_temperature, reverse_string]] model = ChatOpenAI(temperature=0).bind(functions=functions) def run_agent(user_input: str): intermediate_steps = [] while True: result = chain.invoke({"input": user_input, "agent_scratchpad": intermediate_steps}) if result.finish: # AgentFinish return result.output # execute tool and update intermediate_steps obs = tools[result.tool].run(result.tool_input) intermediate_steps.append((result, obs))
  • You can wrap this in a reusable agent_executor to handle:

    • JSON parsing errors

    • Tool execution errors

    • Multi-step actions


5. Adding Chat Memory

  • Memory lets the agent remember previous interactions.

  • Create a messages placeholder in the prompt to maintain chat history:

python
from langchain.chains import ConversationChain from langchain.memory import ConversationBufferMemory memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
  • Pass memory to agent_executor so it can recall previous messages:

python
agent_executor = AgentExecutor(agent_chain=chain, tools=tools, memory=memory, verbose=True)

6. Multi-Tool and Multi-Hop Capabilities

  • Agent can:

    • Decide between multiple tools automatically

    • Chain multiple tools for multi-step reasoning

  • Example:

python
agent_executor.run("What is the weather in SF and give a Wikipedia summary about SF")
  • Agent:

    1. Calls get_current_temperature

    2. Calls search_wikipedia

    3. Returns combined results


7. Creating a Chatbot UI

  • Use Panel or Gradio for a simple dashboard:

python
import panel as pn pn.extension() def respond(query): return agent_executor.run(query) pn.widgets.TextInput(value="", placeholder="Ask me something...", name="Your question", width=400, on_enter=respond)
  • User can chat with agent, and agent will:

    • Maintain conversation history

    • Use tools

    • Return responses dynamically


✅ Resulting Features

  1. Conversational agent with multi-step reasoning.

  2. Tool execution (Wikipedia, Weather, custom tools).

  3. Chat memory (remembers previous messages).

  4. Multi-hop actions.

  5. Optionally, a dashboard UI for real-time interaction.

------------------------------------------------------------------------------------------------------------------------------------------------

VII. Final Conversational Agent Architecture

1. Tools

You have three tools:

  1. Weather Tool – get_current_temperature

    • Uses Open-Meteo API to fetch current temperature given latitude & longitude.

  2. Wikipedia Tool – search_wikipedia

    • Searches Wikipedia and returns summaries of top 3 pages.

  3. Custom Tool – create_your_own

    • Currently reverses a string, but you can replace with any custom function.

python
@tool def create_your_own(query: str) -> str: return query[::-1] # Example: reverse the input

2. Chat Model & Function Formatting

  • You wrap the tools as OpenAI functions using:

python
from langchain.tools.render import format_tool_to_openai_function functions = [format_tool_to_openai_function(f) for f in tools]
  • Then you bind them to a ChatOpenAI model:

python
from langchain.chat_models import ChatOpenAI model = ChatOpenAI(temperature=0).bind(functions=functions)

3. Prompt Template

  • Includes:

    • system message → sets assistant personality.

    • chat_history → maintains conversation memory.

    • agent_scratchpad → keeps track of intermediate tool calls.

python
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder prompt = ChatPromptTemplate.from_messages([ ("system", "You are helpful but sassy assistant"), MessagesPlaceholder(variable_name="chat_history"), ("user", "{input}"), MessagesPlaceholder(variable_name="agent_scratchpad") ])

4. Agent Chain

  • Uses RunnablePassthrough to pre-format intermediate steps for the scratchpad:

python
from langchain.schema.runnable import RunnablePassthrough from langchain.agents.output_parsers import OpenAIFunctionsAgentOutputParser agent_chain = RunnablePassthrough.assign( agent_scratchpad=lambda x: format_to_openai_functions(x["intermediate_steps"]) ) | prompt | model | OpenAIFunctionsAgentOutputParser()

5. Memory

  • ConversationBufferMemory stores previous messages to allow multi-turn chat:

python
from langchain.memory import ConversationBufferMemory memory = ConversationBufferMemory(return_messages=True, memory_key="chat_history")

6. Agent Executor

  • Combines the agent chain, tools, memory, and error handling.

  • Handles tool calls, multi-step reasoning, and returns the response:

python
from langchain.agents import AgentExecutor agent_executor = AgentExecutor(agent=agent_chain, tools=tools, verbose=True, memory=memory)
  • Example usage:

python
agent_executor.invoke({"input": "my name is Bob"}) agent_executor.invoke({"input": "what is my name?"}) agent_executor.invoke({"input": "what's the weather in SF?"})

7. Chatbot GUI with Panel

  • Panel UI binds user input to the agent executor.

  • Displays conversation as rows of User and ChatBot messages.

python
import panel as pn pn.extension() inp = pn.widgets.TextInput(placeholder='Enter text here…') conversation = pn.bind(cb.convchain, inp) dashboard = pn.Column( pn.Row(pn.pane.Markdown('# QnA_Bot')), pn.Tabs(('Conversation', pn.Column( pn.Row(inp), pn.layout.Divider(), pn.panel(conversation, loading_indicator=True, height=400), pn.layout.Divider(), ))) ) dashboard
  • Your cbfs class orchestrates:

    • Calling the agent executor

    • Updating memory

    • Updating the GUI


8. Features

  • Multi-turn conversation with memory.

  • Tool usage (weather, Wikipedia, custom tools).

  • Multi-step reasoning (can call multiple tools in sequence).

  • Interactive dashboard UI.

  • Extendable with new tools or custom functionality.

------------------------------------------------------------------------------------------------------------------------------------------------

VIII. Conclusion


  • OpenAI Function Calling – You learned how to let the language model decide when and how to call functions, enabling structured interactions with external tools or APIs.

  • LangChain Expression Language (LEL) – You saw how to use expressions to transform, extract, and handle structured data programmatically.

  • Tool Usage & Selection – You explored how to define tools (like weather or Wikipedia queries) and have the agent dynamically select which to use based on user input.

  • Building a Conversational Agent – All the concepts came together to create a ChatGPT-like agent that can:

    • Maintain multi-turn conversations using memory

    • Call tools as needed

    • Display results in a GUI

With these skills, you can now build intelligent, tool-using chatbots, automation agents, or data extraction pipelines tailored to your own use cases.

It’s an exciting time in AI—these building blocks let you turn language models into actionable agents, not just conversational interfaces.

No comments:

Post a Comment

Unlocking the Power of LLMs: Functions, Tools, and Agents with LangChain

Large Language Models (LLMs) have completely transformed the way humans interact with technology. Once limited to generating text for human ...