diff --git a/13-LangChain-Expression-Language/03-RunnableLambda.ipynb b/13-LangChain-Expression-Language/03-RunnableLambda.ipynb new file mode 100644 index 000000000..328f18846 --- /dev/null +++ b/13-LangChain-Expression-Language/03-RunnableLambda.ipynb @@ -0,0 +1,427 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "7ddf2db4", + "metadata": {}, + "source": [ + "# RunnableLambda\n", + "\n", + "- Author: [Kenny Jung](https://www.linkedin.com/in/kwang-yong-jung)\n", + "- Design:\n", + "- Peer Review: [Junseong Kim](https://www.linkedin.com/in/%EC%A4%80%EC%84%B1-%EA%B9%80-591b351b2/), [Haseom Shin](https://github.com/IHAGI-c)\n", + "- This is a part of [LangChain Open Tutorial](https://github.com/LangChain-OpenTutorial/LangChain-OpenTutorial)\n", + "\n", + "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/LangChain-OpenTutorial/LangChain-OpenTutorial/blob/main/13-LangChain-Expression-Language/03-RunnableLambda.ipynb) [![Open in GitHub](https://img.shields.io/badge/Open%20in%20GitHub-181717?style=flat-square&logo=github&logoColor=white)](https://github.com/LangChain-OpenTutorial/LangChain-OpenTutorial/blob/main/13-LangChain-Expression-Language/03-RunnableLambda.ipynb)\n", + "\n", + "\n", + "## Overview\n", + "\n", + "`RunnableLambda` provides a way to **integrate custom functions** into your LangChain pipeline.\n", + "\n", + "This allows you to **define your own custom functions** and execute them using `RunnableLambda` as part of their workflow.\n", + "\n", + "For example, you can build your own logic and execute the functions, performing different tasks such as:\n", + "- Data preprocessing,\n", + "- Calculations,\n", + "- Interactions with external APIs, and\n", + "- Any other custom logic you need in your chain.\n", + "\n", + "\n", + "### Table of Contents\n", + "\n", + "- [Overview](#overview)\n", + "- [Environment Setup](#environment-setup)\n", + "- [How to Execute Custom Functions](#how-to-execute-custom-functions)\n", + "- [Using RunnableConfig as Parameters](#using-runnableconfig-as-parameters)\n", + "\n", + "### References\n", + "\n", + "- [LangChain Python API Reference > langchain: 0.3.29 > runnables > RunnableLambda](https://python.langchain.com/api_reference/core/runnables/langchain_core.runnables.base.RunnableLambda.html)\n", + "- [LangChain Python API Reference > langchain: 0.3.29 > runnables > RunnableConfig](https://python.langchain.com/api_reference/core/runnables/langchain_core.runnables.config.RunnableConfig.html#langchain_core.runnables.config.RunnableConfig)\n", + "- [LangChain Python API Reference > docs > concepts > runnables > Configurable Runnables](https://python.langchain.com/docs/concepts/runnables/#configurable-runnables)\n", + "----" + ] + }, + { + "cell_type": "markdown", + "id": "0f993cae", + "metadata": {}, + "source": [ + "## Environment Setup\n", + "\n", + "Setting up your environment is the first step. See the [Environment Setup](https://wikidocs.net/257836) guide for more details.\n", + "\n", + "**[Note]**\n", + "- The `langchain-opentutorial` is a package of easy-to-use environment setup guidance, useful functions and utilities for tutorials.\n", + "- Check out the [`langchain-opentutorial`](https://github.com/LangChain-OpenTutorial/langchain-opentutorial-pypi) for more details." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "7c1ee933", + "metadata": {}, + "outputs": [], + "source": [ + "%%capture --no-stderr\n", + "%pip install langchain-opentutorial" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "d0715aab", + "metadata": {}, + "outputs": [], + "source": [ + "# Install required packages\n", + "from langchain_opentutorial import package\n", + "\n", + "package.install(\n", + " [\n", + " \"langsmith\",\n", + " \"langchain\",\n", + " \"langchain_core\",\n", + " \"langchain_openai\",\n", + " ],\n", + " verbose=False,\n", + " upgrade=False,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "bb16c354", + "metadata": {}, + "source": [ + "Alternatively, you can set and load `OPENAI_API_KEY` from a `.env` file.\n", + "\n", + "**[Note]** This is only necessary if you haven't already set `OPENAI_API_KEY` in previous steps." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "1d3ee479", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Environment variables have been set successfully.\n" + ] + } + ], + "source": [ + "# Set environment variables\n", + "from langchain_opentutorial import set_env\n", + "\n", + "set_env(\n", + " {\n", + " \"OPENAI_API_KEY\": \"\",\n", + " \"LANGCHAIN_API_KEY\": \"\",\n", + " \"LANGCHAIN_TRACING_V2\": \"true\",\n", + " \"LANGCHAIN_ENDPOINT\": \"https://api.smith.langchain.com\",\n", + " \"LANGCHAIN_PROJECT\": \"ConversationBufferWindowMemory\",\n", + " }\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "0b767b3b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from dotenv import load_dotenv\n", + "\n", + "load_dotenv(override=True)" + ] + }, + { + "cell_type": "markdown", + "id": "2a8ce9e9", + "metadata": {}, + "source": [ + "## How to Execute Custom Functions\n", + "\n", + "**Important Note**\n", + "\n", + "A limitation of `RunnableLambda` is that **custom functions can only accept a single argument**. You can wrap custom functions with `RunnableLambda` to use them in your pipeline.\n", + "\n", + "If you have a function that requires multiple parameters, create a wrapper function:\n", + "1. Accepts a single input (typically a dictionary).\n", + "2. Unpacks this input into multiple arguments inside the wrapper.\n", + "3. Passes these arguments to your original function.\n", + "\n", + "For example:\n", + "\n", + "```python\n", + "# Won't work with RunnableLambda\n", + "def original_function(arg1, arg2, arg3):\n", + " pass\n", + "\n", + "# Will work with RunnableLambda\n", + "def wrapper_function(input_dict):\n", + " return original_function(\n", + " input_dict['arg1'],\n", + " input_dict['arg2'],\n", + " input_dict['arg3']\n", + ")\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "44c99a82", + "metadata": {}, + "outputs": [], + "source": [ + "from operator import itemgetter\n", + "\n", + "from langchain_core.prompts import ChatPromptTemplate\n", + "from langchain_core.runnables import RunnableLambda\n", + "from langchain_core.output_parsers import StrOutputParser\n", + "from langchain_openai import ChatOpenAI\n", + "\n", + "\n", + "# Function for returning the length of the text\n", + "def length_function(text):\n", + " return len(text)\n", + "\n", + "\n", + "# Function for multiplying the length of two texts\n", + "def _multiple_length_function(text1, text2):\n", + " return len(text1) * len(text2)\n", + "\n", + "\n", + "# Wrapper function for connecting the function that receives 2 arguments\n", + "def multiple_length_function(\n", + " _dict,\n", + "): # Function for multiplying the length of two texts\n", + " return _multiple_length_function(_dict[\"text1\"], _dict[\"text2\"])\n", + "\n", + "\n", + "# Create a prompt template\n", + "prompt = ChatPromptTemplate.from_template(\"what is {a} + {b}?\")\n", + "# Initialize the ChatOpenAI model\n", + "model = ChatOpenAI(model=\"gpt-4o-mini\", temperature=0)\n", + "\n", + "# Connect the prompt and model to create a chain\n", + "chain1 = prompt | model\n", + "\n", + "# Chain configuration\n", + "chain = (\n", + " {\n", + " \"a\": itemgetter(\"input_1\") | RunnableLambda(length_function),\n", + " \"b\": {\"text1\": itemgetter(\"input_1\"), \"text2\": itemgetter(\"input_2\")}\n", + " | RunnableLambda(multiple_length_function),\n", + " }\n", + " | prompt\n", + " | model\n", + " | StrOutputParser()\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "959d3031", + "metadata": {}, + "source": [ + "Execute the chain and verify the result." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "60df37cc", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'3 + 9 equals 12.'" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Execute the chain with the given arguments.\n", + "chain.invoke({\"input_1\": \"bar\", \"input_2\": \"gah\"})" + ] + }, + { + "cell_type": "markdown", + "id": "6c2e1d85", + "metadata": {}, + "source": [ + "## Using RunnableConfig as Parameters\n", + "\n", + "`RunnableLambda` can optionally accept a [RunnableConfig](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.config.RunnableConfig.html#langchain_core.runnables.config.RunnableConfig) object.\n", + "\n", + "This allows you to pass various configuration options to nested executions, including:\n", + "- Callbacks: For tracking and monitoring function execution.\n", + "- Tags: For labeling and organizing different runs.\n", + "- Other configuration: Additional settings to control function behavior.\n", + "\n", + "For example, you can use `RunnableConfig` to:\n", + "- Track function performance.\n", + "- Add logging capabilities.\n", + "- Group related operations using tags.\n", + "- Configure error handling and retry logic.\n", + "- Set timeouts and other execution parameters.\n", + "\n", + "This makes `RunnableLambda` highly configurable for complex workflows with fine-grained control over execution." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "0e319229", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.output_parsers import StrOutputParser\n", + "from langchain_core.runnables import RunnableConfig\n", + "import json\n", + "\n", + "\n", + "def parse_or_fix(text: str, config: RunnableConfig):\n", + " # Create a prompt template for fixing the next text\n", + " fixing_chain = (\n", + " ChatPromptTemplate.from_template(\n", + " \"Fix the following text:\\n\\ntext\\n{input}\\n\\nError: {error}\"\n", + " \" Don't narrate, just respond with the fixed data.\"\n", + " )\n", + " | ChatOpenAI(model=\"gpt-4o-mini\", temperature=0)\n", + " | StrOutputParser()\n", + " )\n", + " # Try up to 3 times\n", + " for _ in range(3):\n", + " try:\n", + " # Parse the text as JSON\n", + " return json.loads(text)\n", + " except Exception as e:\n", + " # If parsing fails, call the fixing chain to fix the text\n", + " text = fixing_chain.invoke({\"input\": text, \"error\": e}, config)\n", + " print(f\"config: {config}\")\n", + " # If parsing fails, return \"Failed to parse\"\n", + " return \"Failed to parse\"" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "c8f4269a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "config: {'tags': ['my-tag'], 'metadata': {}, 'callbacks': , 'recursion_limit': 25, 'configurable': {}}\n", + "\n", + "\n", + "Modified result:\n", + "{'foo': 'bar'}\n" + ] + } + ], + "source": [ + "from langchain.callbacks import get_openai_callback\n", + "\n", + "with get_openai_callback() as cb:\n", + " # Call the parse_or_fix function using RunnableLambda\n", + " output = RunnableLambda(parse_or_fix).invoke(\n", + " input=\"{foo:: bar}\",\n", + " config={\"tags\": [\"my-tag\"], \"callbacks\": [cb]}, # Pass the config\n", + " )\n", + " # Print the modified result\n", + " print(f\"\\n\\nModified result:\\n{output}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "7da80691", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'foo': 'bar'}\n" + ] + } + ], + "source": [ + "# Check the output\n", + "print(output)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "f960a244", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Tokens Used: 59\n", + "\tPrompt Tokens: 52\n", + "\t\tPrompt Tokens Cached: 0\n", + "\tCompletion Tokens: 7\n", + "\t\tReasoning Tokens: 0\n", + "Successful Requests: 1\n", + "Total Cost (USD): $1.1999999999999997e-05\n" + ] + } + ], + "source": [ + "# Check the callback\n", + "print(cb)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "opentutorial", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}