diff --git a/17-LangGraph/03-Use-Cases/02-LangGraph-Prompt-Generation.ipynb b/17-LangGraph/03-Use-Cases/02-LangGraph-Prompt-Generation.ipynb new file mode 100644 index 000000000..fe657d3fc --- /dev/null +++ b/17-LangGraph/03-Use-Cases/02-LangGraph-Prompt-Generation.ipynb @@ -0,0 +1,851 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "aeb119f6", + "metadata": {}, + "source": [ + "# Meta Prompt Generator based on User Requirements\n", + "\n", + "- Author: [Kenny Jung](https://www.linkedin.com/in/kwang-yong-jung)\n", + "- Design:\n", + "- Peer Review: \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/17-LangGraph/03-Use-Case/rag/02-LangGraph-Prompt-Generation.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/17-LangGraph/03-Use-Case/rag/02-LangGraph-Prompt-Generation.ipynb)\n", + "\n", + "\n", + "## Overview\n", + "\n", + "This tutorial explains how to create a chatbot that helps users generate prompts. The chatbot first collects requirements from users, then generates prompts based on these requirements and modifies them according to user input. This process is divided into two separate states, with the LLM determining when to transition between states.\n", + " \n", + "A graphical representation of this system can be found below.\n", + " \n", + "**Key Topics Covered**\n", + " \n", + "- **Gather information**: Defining graphs for collecting user requirements\n", + "- **Generate Prompt**: Setting up states for prompt generation\n", + "- **Define the state logic**: Defining the chatbot's state logic\n", + "- **Create the graph**: Creating graphs and storing conversation history\n", + "- **Use the graph**: How to use the generated chatbot\n", + " \n", + "In this example, we create a chatbot that helps users generate prompts.\n", + " \n", + "The chatbot first collects requirements from users, then generates prompts based on these requirements and modifies them according to user input.\n", + " \n", + "This process is divided into two separate states, with the LLM determining when to transition between states.\n", + " \n", + "A graphical representation of the system can be found below.\n", + "\n", + "\n", + "### Table of Contents\n", + "\n", + "- [Overview](#overview)\n", + "- [Environment Setup](#environment-setup)\n", + "- [Collecting user requirements](#collecting-user-requirements)\n", + "- [Prompt Generation](#prompt-generation)\n", + "- [Define state logic](#define-state-logic)\n", + "- [Create the graph](#create-the-graph)\n", + "- [Run the graph](#run-the-graph)\n", + "\n", + "\n", + "\n", + "![](./assets/02-LangGraph-Prompt-Generation-meta-prompt-generator.png)\n", + "\n", + "----" + ] + }, + { + "cell_type": "markdown", + "id": "1e6652c9", + "metadata": {}, + "source": [ + "## Environment Setup\n", + "\n", + "Set up the environment. You may refer to [Environment Setup](https://wikidocs.net/257836) for more details.\n", + "\n", + "**[Note]**\n", + "- `langchain-opentutorial` is a package that provides a set of easy-to-use environment setup, useful functions and utilities for tutorials. \n", + "- You can check out the [`langchain-opentutorial`](https://github.com/LangChain-OpenTutorial/langchain-opentutorial-pypi) for more details." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "b8fa29b5", + "metadata": {}, + "outputs": [], + "source": [ + "%%capture --no-stderr\n", + "%pip install langchain-opentutorial" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "7e2c0948", + "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", + " \"langchain_community\",\n", + " ],\n", + " verbose=False,\n", + " upgrade=False,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "7ea4f716", + "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\": \"02-LangGraph-Prompt-Generation\",\n", + " }\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "250d9b81", + "metadata": {}, + "source": [ + "You can alternatively set `OPENAI_API_KEY` in `.env` file and load it.\n", + "\n", + "[Note] This is not necessary if you've already set `OPENAI_API_KEY` in previous steps." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "9ebcf307", + "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": "e65d330b", + "metadata": {}, + "source": [ + "## Collecting user requirements\n", + "\n", + "At first, we define a node that collects user requirements.\n", + "\n", + "In this process, we can request specific information from the user. We request the necessary information from the user until all necessary information is **satisfied**." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "fb32ec35", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import List\n", + "from langchain_core.messages import SystemMessage\n", + "from langchain_openai import ChatOpenAI\n", + "from pydantic import BaseModel\n", + "\n", + "# System message template for collecting user requirements\n", + "template = \"\"\"Your job is to get information from a user about what type of prompt template they want to create.\n", + "\n", + "You should get the following information from them:\n", + "\n", + "- What the objective of the prompt is\n", + "- What variables will be passed into the prompt template\n", + "- Any constraints for what the output should NOT do\n", + "- Any requirements that the output MUST adhere to\n", + "\n", + "If you are not able to discern this info, ask them to clarify! Do not attempt to wildly guess.\n", + "\n", + "After you are able to discern all the information, call the relevant tool.\"\"\"\n", + "\n", + "\n", + "# Get user message list and combine with system message\n", + "def get_messages_info(messages):\n", + " # Combine system message and existing messages for collecting user requirements\n", + " return [SystemMessage(content=template)] + messages\n", + "\n", + "\n", + "# Data model for defining instructions on how to prompt the LLM\n", + "class PromptInstructions(BaseModel):\n", + " \"\"\"Instructions on how to prompt the LLM.\"\"\"\n", + "\n", + " # Objective of the prompt\n", + " objective: str\n", + " # List of variables to be passed into the prompt template\n", + " variables: List[str]\n", + " # List of constraints to avoid in the output\n", + " constraints: List[str]\n", + " # List of requirements that the output must adhere to\n", + " requirements: List[str]\n", + "\n", + "\n", + "# Initialize LLM\n", + "llm = ChatOpenAI(model=\"gpt-4o-mini\", temperature=0)\n", + "# Bind PromptInstructions structure\n", + "llm_with_tool = llm.bind_tools([PromptInstructions])\n", + "\n", + "\n", + "# Create a message chain based on state information and call the LLM\n", + "def info_chain(state):\n", + " # Get message information from state and combine with system message\n", + " messages = get_messages_info(state[\"messages\"])\n", + " # Call LLM to generate response\n", + " response = llm_with_tool.invoke(messages)\n", + " # Return generated response as message list\n", + " return {\"messages\": [response]}" + ] + }, + { + "cell_type": "markdown", + "id": "9f314f44", + "metadata": {}, + "source": [ + "## Prompt Generation\n", + "\n", + "Now, we set the state to generate prompts. \n", + "\n", + "To do this, we need a separate system message and a function to filter all messages before the tool call.\n", + "\n", + "The definition of the meta prompt we use here is as follows.\n", + "\n", + "**Meta Prompt (Meta Prompt) Definition**\n", + "\n", + "Meta Prompt is a concept that refers to **methods or strategies for optimizing prompt design and creation**, which is used to make AI language models more effective and efficient. It goes beyond simply inputting text, **including structured and creative approaches to guide model responses or improve the quality of results**.\n", + "\n", + "**Key Features**\n", + "\n", + "1. **Goal-oriented structure** \n", + " Meta prompt includes a clear definition of the information you want to achieve and a step-by-step design process for it.\n", + "\n", + "2. **Adaptive design** \n", + " Consider the model's response characteristics, limitations, and strengths to modify or iteratively optimize the prompt.\n", + "\n", + "3. **Useful prompt engineering** \n", + " Include conditional statements, guidelines, role instructions, etc. to finely adjust the model's response.\n", + "\n", + "4. **Multilayer approach** \n", + " Do not stop at a single question, but adopt a method of incrementally refining the answer through sub-questions.\n", + "\n", + "\n", + "- Reference: [OpenAI Meta Prompt Engineering Guide](https://platform.openai.com/docs/guides/prompt-generation?context=text-out)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "3564d908", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_core.messages import AIMessage, HumanMessage, ToolMessage, SystemMessage\n", + "\n", + "# Define meta prompt for generating prompts (reference OpenAI Meta Prompt Engineering Guide)\n", + "META_PROMPT = \"\"\"Given a task description or existing prompt, produce a detailed system prompt to guide a language model in completing the task effectively.\n", + "\n", + "# Guidelines\n", + "\n", + "- Understand the Task: Grasp the main objective, goals, requirements, constraints, and expected output.\n", + "- Minimal Changes: If an existing prompt is provided, improve it only if it's simple. For complex prompts, enhance clarity and add missing elements without altering the original structure.\n", + "- Reasoning Before Conclusions**: Encourage reasoning steps before any conclusions are reached. ATTENTION! If the user provides examples where the reasoning happens afterward, REVERSE the order! NEVER START EXAMPLES WITH CONCLUSIONS!\n", + " - Reasoning Order: Call out reasoning portions of the prompt and conclusion parts (specific fields by name). For each, determine the ORDER in which this is done, and whether it needs to be reversed.\n", + " - Conclusion, classifications, or results should ALWAYS appear last.\n", + "- Examples: Include high-quality examples if helpful, using placeholders [in brackets] for complex elements.\n", + " - What kinds of examples may need to be included, how many, and whether they are complex enough to benefit from placeholders.\n", + "- Clarity and Conciseness: Use clear, specific language. Avoid unnecessary instructions or bland statements.\n", + "- Formatting: Use markdown features for readability. DO NOT USE ``` CODE BLOCKS UNLESS SPECIFICALLY REQUESTED.\n", + "- Preserve User Content: If the input task or prompt includes extensive guidelines or examples, preserve them entirely, or as closely as possible. If they are vague, consider breaking down into sub-steps. Keep any details, guidelines, examples, variables, or placeholders provided by the user.\n", + "- Constants: DO include constants in the prompt, as they are not susceptible to prompt injection. Such as guides, rubrics, and examples.\n", + "- Output Format: Explicitly the most appropriate output format, in detail. This should include length and syntax (e.g. short sentence, paragraph, JSON, etc.)\n", + " - For tasks outputting well-defined or structured data (classification, JSON, etc.) bias toward outputting a JSON.\n", + " - JSON should never be wrapped in code blocks (```) unless explicitly requested.\n", + "\n", + "The final prompt you output should adhere to the following structure below. Do not include any additional commentary, only output the completed system prompt. SPECIFICALLY, do not include any additional messages at the start or end of the prompt. (e.g. no \"---\")\n", + "\n", + "[Concise instruction describing the task - this should be the first line in the prompt, no section header]\n", + "\n", + "[Additional details as needed.]\n", + "\n", + "[Optional sections with headings or bullet points for detailed steps.]\n", + "\n", + "# Steps [optional]\n", + "\n", + "[optional: a detailed breakdown of the steps necessary to accomplish the task]\n", + "\n", + "# Output Format\n", + "\n", + "[Specifically call out how the output should be formatted, be it response length, structure e.g. JSON, markdown, etc]\n", + "\n", + "[User given variables should be wrapped in {{brackets}}]\n", + "\n", + "\n", + "{{question}}\n", + "\n", + "\n", + "\n", + "{{answer}}\n", + "\n", + "\n", + "# Examples [optional]\n", + "\n", + "[Optional: 1-3 well-defined examples with placeholders if necessary. Clearly mark where examples start and end, and what the input and output are. User placeholders as necessary.]\n", + "[If the examples are shorter than what a realistic example is expected to be, make a reference with () explaining how real examples should be longer / shorter / different. AND USE PLACEHOLDERS! ]\n", + "\n", + "# Notes [optional]\n", + "\n", + "[optional: edge cases, details, and an area to call or repeat out specific important considerations]\n", + "\n", + "# Based on the following requirements, write a good prompt template:\n", + "\n", + "{reqs}\n", + "\"\"\"\n", + "\n", + "\n", + "# Function to get messages for prompt generation\n", + "# Only get messages after tool call\n", + "def get_prompt_messages(messages: list):\n", + " # Initialize variable to store tool call information\n", + " tool_call = None\n", + " # Initialize list to store messages after tool call\n", + " other_msgs = []\n", + " # Iterate through message list and process tool call and other messages\n", + " for m in messages:\n", + " # If AI message has tool call, store tool call information\n", + " if isinstance(m, AIMessage) and m.tool_calls:\n", + " tool_call = m.tool_calls[0][\"args\"]\n", + " # Skip ToolMessage\n", + " elif isinstance(m, ToolMessage):\n", + " continue\n", + " # Add messages after tool call to list\n", + " elif tool_call is not None:\n", + " other_msgs.append(m)\n", + " # Combine system message and messages after tool call and return\n", + " return [SystemMessage(content=META_PROMPT.format(reqs=tool_call))] + other_msgs\n", + "\n", + "\n", + "# Define function for prompt generation chain\n", + "def prompt_gen_chain(state):\n", + " # Get prompt messages from state\n", + " messages = get_prompt_messages(state[\"messages\"])\n", + " # Call LLM to generate response\n", + " response = llm.invoke(messages)\n", + " # Return generated response as message list\n", + " return {\"messages\": [response]}" + ] + }, + { + "cell_type": "markdown", + "id": "c9c4d636", + "metadata": {}, + "source": [ + "## Define state logic\n", + "\n", + "We describe the logic for determining the state of the chatbot.\n", + "\n", + "- If the last message is a `tool call`, the chatbot is in the \"prompt creator\"(`prompt`) state.\n", + "- If the last message is not a `HumanMessage`, the user needs to respond next, so the chatbot is in the `END` state.\n", + "- If the last message is a `HumanMessage`, the chatbot is in the `prompt` state if there was a `tool call` before.\n", + "- Otherwise, the chatbot is in the `info` state." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "e725b748", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.graph import END\n", + "\n", + "\n", + "# Define function for determining state\n", + "# Get message list from state\n", + "def get_state(state):\n", + " messages = state[\"messages\"]\n", + " # If the last message is aAIMessage and has tool call\n", + " if isinstance(messages[-1], AIMessage) and messages[-1].tool_calls:\n", + " # Return state for adding tool message\n", + " return \"add_tool_message\"\n", + " # If the last message is not a HumanMessage\n", + " elif not isinstance(messages[-1], HumanMessage):\n", + " # Return end state\n", + " return END\n", + " # Otherwise, return info state\n", + " return \"info\"" + ] + }, + { + "cell_type": "markdown", + "id": "3f3150ab", + "metadata": {}, + "source": [ + "## Create the graph\n", + "\n", + "Now, we can create a graph. We will use `MemorySaver` to save the conversation history." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "2e03b383", + "metadata": {}, + "outputs": [], + "source": [ + "from langgraph.checkpoint.memory import MemorySaver\n", + "from langgraph.graph import StateGraph, START, END\n", + "from langchain_core.messages import ToolMessage\n", + "from langgraph.graph.message import add_messages\n", + "from typing import Annotated\n", + "from typing_extensions import TypedDict\n", + "\n", + "\n", + "# Define State\n", + "class State(TypedDict):\n", + " messages: Annotated[list, add_messages]\n", + "\n", + "\n", + "# Initialize MemorySaver for saving conversation history\n", + "memory = MemorySaver()\n", + "\n", + "# Initialize state graph\n", + "workflow = StateGraph(State)\n", + "\n", + "# Add nodes\n", + "workflow.add_node(\"info\", info_chain)\n", + "workflow.add_node(\"prompt\", prompt_gen_chain)\n", + "\n", + "\n", + "# Define node for adding tool message\n", + "@workflow.add_node\n", + "def add_tool_message(state: State):\n", + " return {\n", + " \"messages\": [\n", + " ToolMessage(\n", + " content=\"Prompt generated!\",\n", + " tool_call_id=state[\"messages\"][-1].tool_calls[0][\n", + " \"id\"\n", + " ], # Get tool call ID from state and add to message\n", + " )\n", + " ]\n", + " }\n", + "\n", + "\n", + "# Define conditional state transition\n", + "workflow.add_conditional_edges(\"info\", get_state, [\"add_tool_message\", \"info\", END])\n", + "\n", + "# Define edges\n", + "workflow.add_edge(\"add_tool_message\", \"prompt\")\n", + "workflow.add_edge(\"prompt\", END)\n", + "workflow.add_edge(START, \"info\")\n", + "\n", + "# Compile graph\n", + "graph = workflow.compile(checkpointer=memory)" + ] + }, + { + "cell_type": "markdown", + "id": "ea9d8b24", + "metadata": {}, + "source": [ + "Visualize the graph.\n", + "\n", + "https://langchain-ai.github.io/langgraph/how-tos/visualization/?h=visuali#setup" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "63272588", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import Image, display\n", + "from langchain_core.runnables.graph import CurveStyle, MermaidDrawMethod, NodeStyles\n", + "\n", + "display(\n", + " Image(\n", + " graph.get_graph().draw_mermaid_png(\n", + " draw_method=MermaidDrawMethod.API,\n", + " )\n", + " )\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "8e3849ce", + "metadata": {}, + "source": [ + "## Run the graph\n", + "\n", + "Now, we can run the graph to generate prompts." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "87a397ab", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\u001b[93m[User] I need a biz trip plan to SF from Feb 1 to 3. This is for a meeting with partner ABC on 2nd.\u001b[0m\n", + "\u001b[94m[AI] I need a biz trip plan to SF from Feb 1 to 3. This is for a meeting with partner ABC on 2nd.\u001b[0m\n", + "\u001b[94m[AI] To help you create a prompt template for your business trip plan, I need a bit more information:\n", + "\n", + "1. **What is the objective of the prompt?** (e.g., to create a detailed itinerary, suggest places to stay, etc.)\n", + "2. **What variables will be passed into the prompt template?** (e.g., dates, location, meeting details, etc.)\n", + "3. **Are there any constraints for what the output should NOT do?** (e.g., avoid suggesting certain types of accommodations, etc.)\n", + "4. **What requirements must the output adhere to?** (e.g., must include transportation options, must be within a certain budget, etc.)\n", + "\n", + "Please provide these details so I can assist you better!\u001b[0m\n", + "\n", + "\u001b[93m[User] need to create itinerary, with variable meeting location, constraint for mid-level accommodation, under budget $3,000\u001b[0m\n", + "\u001b[94m[AI] I need a biz trip plan to SF from Feb 1 to 3. This is for a meeting with partner ABC on 2nd.\u001b[0m\n", + "\u001b[94m[AI] To help you create a prompt template for your business trip plan, I need a bit more information:\n", + "\n", + "1. **What is the objective of the prompt?** (e.g., to create a detailed itinerary, suggest places to stay, etc.)\n", + "2. **What variables will be passed into the prompt template?** (e.g., dates, location, meeting details, etc.)\n", + "3. **Are there any constraints for what the output should NOT do?** (e.g., avoid suggesting certain types of accommodations, etc.)\n", + "4. **What requirements must the output adhere to?** (e.g., must include transportation options, must be within a certain budget, etc.)\n", + "\n", + "Please provide these details so I can assist you better!\u001b[0m\n", + "\u001b[94m[AI] need to create itinerary, with variable meeting location, constraint for mid-level accommodation, under budget $3,000\u001b[0m\n", + "\u001b[94m[AI] Thank you for the information! Just to clarify:\n", + "\n", + "1. **Objective:** Create an itinerary for a business trip.\n", + "2. **Variables:** Meeting location, dates (Feb 1 to 3), partner name (ABC).\n", + "3. **Constraints:** Avoid suggesting high-end accommodations; must be mid-level.\n", + "4. **Requirements:** The total cost must be under $3,000.\n", + "\n", + "Is there anything else you would like to add or modify? If not, I will proceed to create the prompt template with the information provided.\u001b[0m\n", + "\n", + "\u001b[93m[User] proceed to create the prompt template with the information provided\u001b[0m\n", + "\u001b[94m[AI] I need a biz trip plan to SF from Feb 1 to 3. This is for a meeting with partner ABC on 2nd.\u001b[0m\n", + "\u001b[94m[AI] To help you create a prompt template for your business trip plan, I need a bit more information:\n", + "\n", + "1. **What is the objective of the prompt?** (e.g., to create a detailed itinerary, suggest places to stay, etc.)\n", + "2. **What variables will be passed into the prompt template?** (e.g., dates, location, meeting details, etc.)\n", + "3. **Are there any constraints for what the output should NOT do?** (e.g., avoid suggesting certain types of accommodations, etc.)\n", + "4. **What requirements must the output adhere to?** (e.g., must include transportation options, must be within a certain budget, etc.)\n", + "\n", + "Please provide these details so I can assist you better!\u001b[0m\n", + "\u001b[94m[AI] need to create itinerary, with variable meeting location, constraint for mid-level accommodation, under budget $3,000\u001b[0m\n", + "\u001b[94m[AI] Thank you for the information! Just to clarify:\n", + "\n", + "1. **Objective:** Create an itinerary for a business trip.\n", + "2. **Variables:** Meeting location, dates (Feb 1 to 3), partner name (ABC).\n", + "3. **Constraints:** Avoid suggesting high-end accommodations; must be mid-level.\n", + "4. **Requirements:** The total cost must be under $3,000.\n", + "\n", + "Is there anything else you would like to add or modify? If not, I will proceed to create the prompt template with the information provided.\u001b[0m\n", + "\u001b[94m[AI] proceed to create the prompt template with the information provided\u001b[0m\n", + "\u001b[94m[AI] \u001b[0m\n", + "\u001b[94m[AI] Prompt generated!\u001b[0m\n", + "\u001b[94m[AI] Create a detailed itinerary for a business trip to San Francisco from February 1 to 3, including a meeting with partner ABC on February 2.\n", + "\n", + "- The itinerary should include travel arrangements, accommodation, and meal options.\n", + "- Ensure that the meeting location is specified and convenient for both parties.\n", + "- The total cost of the trip must be under $3,000.\n", + "- Avoid suggesting high-end accommodations; focus on mid-level options.\n", + "\n", + "# Steps\n", + "\n", + "1. Determine travel options to San Francisco, including flights and transportation.\n", + "2. Identify mid-level accommodation options for the duration of the stay.\n", + "3. Plan meals, considering local dining options that fit within the budget.\n", + "4. Schedule the meeting with partner ABC, including the location and time.\n", + "\n", + "# Output Format\n", + "\n", + "The output should be formatted as a structured itinerary in JSON format, detailing each component of the trip, including travel, accommodation, meals, and the meeting.\n", + "\n", + "{\n", + " \"trip\": {\n", + " \"dates\": {\n", + " \"start\": \"February 1\",\n", + " \"end\": \"February 3\"\n", + " },\n", + " \"travel\": {\n", + " \"flights\": [\n", + " {\n", + " \"departure\": \"{departure_city}\",\n", + " \"arrival\": \"San Francisco\",\n", + " \"cost\": \"{flight_cost}\"\n", + " }\n", + " ],\n", + " \"transportation\": [\n", + " {\n", + " \"type\": \"Uber\",\n", + " \"cost\": \"{transportation_cost}\"\n", + " }\n", + " ]\n", + " },\n", + " \"accommodation\": {\n", + " \"name\": \"{hotel_name}\",\n", + " \"cost\": \"{hotel_cost}\"\n", + " },\n", + " \"meals\": [\n", + " {\n", + " \"day\": \"February 1\",\n", + " \"options\": [\n", + " {\n", + " \"restaurant\": \"{restaurant_name}\",\n", + " \"cost\": \"{meal_cost}\"\n", + " }\n", + " ]\n", + " }\n", + " ],\n", + " \"meeting\": {\n", + " \"partner\": \"ABC\",\n", + " \"location\": \"{meeting_location}\",\n", + " \"date\": \"February 2\"\n", + " },\n", + " \"total_cost\": \"{total_cost}\"\n", + " }\n", + "}\n", + "\n", + "# Examples\n", + "\n", + "**Example 1:**\n", + "Input:\n", + "{\n", + " \"departure_city\": \"New York\",\n", + " \"meeting_location\": \"ABC Headquarters\",\n", + " \"hotel_name\": \"Midtown Inn\",\n", + " \"flight_cost\": 500,\n", + " \"transportation_cost\": 100,\n", + " \"hotel_cost\": 600,\n", + " \"meal_cost\": 150,\n", + " \"total_cost\": 1450\n", + "}\n", + "Output:\n", + "{\n", + " \"trip\": {\n", + " \"dates\": {\n", + " \"start\": \"February 1\",\n", + " \"end\": \"February 3\"\n", + " },\n", + " \"travel\": {\n", + " \"flights\": [\n", + " {\n", + " \"departure\": \"New York\",\n", + " \"arrival\": \"San Francisco\",\n", + " \"cost\": 500\n", + " }\n", + " ],\n", + " \"transportation\": [\n", + " {\n", + " \"type\": \"Uber\",\n", + " \"cost\": 100\n", + " }\n", + " ]\n", + " },\n", + " \"accommodation\": {\n", + " \"name\": \"Midtown Inn\",\n", + " \"cost\": 600\n", + " },\n", + " \"meals\": [\n", + " {\n", + " \"day\": \"February 1\",\n", + " \"options\": [\n", + " {\n", + " \"restaurant\": \"Local Diner\",\n", + " \"cost\": 150\n", + " }\n", + " ]\n", + " }\n", + " ],\n", + " \"meeting\": {\n", + " \"partner\": \"ABC\",\n", + " \"location\": \"ABC Headquarters\",\n", + " \"date\": \"February 2\"\n", + " },\n", + " \"total_cost\": 1450\n", + " }\n", + "}\n", + "\n", + "**Example 2:**\n", + "Input:\n", + "{\n", + " \"departure_city\": \"Chicago\",\n", + " \"meeting_location\": \"ABC Conference Room\",\n", + " \"hotel_name\": \"Business Suites\",\n", + " \"flight_cost\": 400,\n", + " \"transportation_cost\": 80,\n", + " \"hotel_cost\": 500,\n", + " \"meal_cost\": 120,\n", + " \"total_cost\": 1200\n", + "}\n", + "Output:\n", + "{\n", + " \"trip\": {\n", + " \"dates\": {\n", + " \"start\": \"February 1\",\n", + " \"end\": \"February 3\"\n", + " },\n", + " \"travel\": {\n", + " \"flights\": [\n", + " {\n", + " \"departure\": \"Chicago\",\n", + " \"arrival\": \"San Francisco\",\n", + " \"cost\": 400\n", + " }\n", + " ],\n", + " \"transportation\": [\n", + " {\n", + " \"type\": \"Uber\",\n", + " \"cost\": 80\n", + " }\n", + " ]\n", + " },\n", + " \"accommodation\": {\n", + " \"name\": \"Business Suites\",\n", + " \"cost\": 500\n", + " },\n", + " \"meals\": [\n", + " {\n", + " \"day\": \"February 1\",\n", + " \"options\": [\n", + " {\n", + " \"restaurant\": \"City Grill\",\n", + " \"cost\": 120\n", + " }\n", + " ]\n", + " }\n", + " ],\n", + " \"meeting\": {\n", + " \"partner\": \"ABC\",\n", + " \"location\": \"ABC Conference Room\",\n", + " \"date\": \"February 2\"\n", + " },\n", + " \"total_cost\": 1200\n", + " }\n", + "}\n", + "\n", + "# Notes\n", + "\n", + "- Ensure all costs are estimated and within the specified budget.\n", + "- Consider local transportation options that are economical and convenient.\n", + "- The meeting location should be easily accessible from the hotel.\u001b[0m\n", + "\n", + "\u001b[93m[User] q\u001b[0m\n", + "AI: See you next time!\n" + ] + } + ], + "source": [ + "import uuid\n", + "from langchain_core.messages import HumanMessage\n", + "\n", + "# Initialize configuration with unique thread_id\n", + "config = {\"configurable\": {\"thread_id\": str(uuid.uuid4())}}\n", + "\n", + "# Start infinite loop\n", + "while True:\n", + " try:\n", + " # Get user input\n", + " user_input = input(\"User (q/Q to quit): \")\n", + " except EOFError:\n", + " break\n", + "\n", + " # Print user input\n", + " print(f\"\\n\\033[93m[User] {user_input}\\033[0m\")\n", + "\n", + " # If user input is 'q' or 'Q', break the loop\n", + " if user_input.lower() == \"q\":\n", + " print(\"AI: See you next time!\")\n", + " break\n", + "\n", + " # Create a state with the user's message\n", + " state = {\"messages\": [HumanMessage(content=user_input)]}\n", + "\n", + " # Process the graph with the current state\n", + " result = graph.invoke(state, config=config) # Use invoke() instead of run()\n", + "\n", + " # Print the AI's response\n", + " for message in result[\"messages\"]:\n", + " print(f\"\\033[94m[AI] {message.content}\\033[0m\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "langchain-kr-lwwSZlnu-py3.11", + "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 +} diff --git a/17-LangGraph/03-Use-Cases/assets/02-LangGraph-Prompt-Generation-meta-prompt-generator.png b/17-LangGraph/03-Use-Cases/assets/02-LangGraph-Prompt-Generation-meta-prompt-generator.png new file mode 100644 index 000000000..c98cc817c Binary files /dev/null and b/17-LangGraph/03-Use-Cases/assets/02-LangGraph-Prompt-Generation-meta-prompt-generator.png differ