diff --git a/examples/chatbots/imgs/prompt-generator.png b/examples/chatbots/imgs/prompt-generator.png new file mode 100644 index 000000000..6bcc7083f Binary files /dev/null and b/examples/chatbots/imgs/prompt-generator.png differ diff --git a/examples/chatbots/prompt-generator.ipynb b/examples/chatbots/prompt-generator.ipynb index 80ff8a061..b89fa4247 100644 --- a/examples/chatbots/prompt-generator.ipynb +++ b/examples/chatbots/prompt-generator.ipynb @@ -1,15 +1,51 @@ { "cells": [ + { + "cell_type": "markdown", + "id": "d72fae4e-f7de-42b7-91ee-bdd0a57ae46c", + "metadata": {}, + "source": [ + "# Prompt Generator\n", + "\n", + "In this example we will create a chat bot that helps a user generate a prompt.\n", + "It will first collect requirements from the user, and then will generate the prompt (and refine it based on user input).\n", + "These are split into two separate states, and the LLM decides when to transition between them.\n", + "\n", + "A graphical representation of the system can be found below.\n", + "\n", + "![](imgs/prompt-generator.png)" + ] + }, + { + "cell_type": "markdown", + "id": "6d78b593-ba26-4c90-b2e2-83119e47679f", + "metadata": {}, + "source": [ + "## Gather information\n", + "\n", + "First, let's define the part of the graph that will gather user requirements. This will be an LLM call with a specific system message. It will have access to a tool that it can call when it is ready to generate the prompt." + ] + }, { "cell_type": "code", - "execution_count": 1, - "id": "c0aa20b8-fe59-4c91-a06f-32002f142a41", + "execution_count": 6, + "id": "53216ab5-2cd3-48a4-8778-41ba10f72519", "metadata": {}, "outputs": [], "source": [ - "from langgraph.graph import MessageGraph\n", "from langchain_core.messages import SystemMessage\n", - "\n", + "from langchain_openai import ChatOpenAI\n", + "from langchain_core.pydantic_v1 import BaseModel, Field\n", + "from typing import List" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "5f795b78-004d-40ca-95d6-069f67e4f9c9", + "metadata": {}, + "outputs": [], + "source": [ "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", @@ -21,35 +57,87 @@ "\n", "If you are not able to discerne this info, ask them to clarify! Do not attempt to wildly guess.\n", "\n", - "After you are able to discerne all the information, call the relevant tool\"\"\"\n", + "After you are able to discerne all the information, call the relevant tool\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "153a7c89-ec75-430e-96ac-6294d4e6ea2b", + "metadata": {}, + "outputs": [], + "source": [ + "llm = ChatOpenAI(temperature=0)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "8325ed0e-177a-4129-b4a4-cd54d527c634", + "metadata": {}, + "outputs": [], + "source": [ "def get_messages_info(messages):\n", " return [SystemMessage(content=template)] + messages\n", "\n", - "from langchain_openai import ChatOpenAI\n", - "\n", - "llm = ChatOpenAI(temperature=0)\n", - "\n", - "from langchain_core.pydantic_v1 import BaseModel, Field\n", - "from typing import List\n", - "\n", "\n", "class PromptInstructions(BaseModel):\n", " \"\"\"Instructions on how to prompt the LLM.\"\"\"\n", " objective: str\n", " variables: List[str]\n", " constraints: List[str]\n", - " requirements: List[str]\n", - "\n", + " requirements: List[str]" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "95bb557a-47f2-46fd-adf7-9cf0e8ffe51c", + "metadata": {}, + "outputs": [], + "source": [ "llm_with_tool = llm.bind_tools([PromptInstructions])\n", "\n", - "chain = get_messages_info | llm_with_tool\n", + "chain = get_messages_info | llm_with_tool" + ] + }, + { + "cell_type": "markdown", + "id": "bb40630f-83c7-4283-a6dd-04231805a7ed", + "metadata": {}, + "source": [ + "## Generate Prompt\n", "\n", + "We now set up the state that will generate the prompt.\n", + "This will require a separate system message, as well as a function to filter out all message PRIOR to the tool invocation (as that is when the previous state decided it was time to generate the prompt" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "5b6736fb-21d7-44d3-a3fe-460f15945654", + "metadata": {}, + "outputs": [], + "source": [ + "# Helper function for determining if tool was called\n", "def _is_tool_call(msg):\n", - " return hasattr(msg, \"additional_kwargs\") and 'tool_calls' in msg.additional_kwargs\n", - "\n", + " return hasattr(msg, \"additional_kwargs\") and 'tool_calls' in msg.additional_kwargs" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "ca9a0234-bbeb-4bff-8276-8dde499c3390", + "metadata": {}, + "outputs": [], + "source": [ + "# New system prompt\n", "prompt_system = \"\"\"Based on the following requirements, write a good prompt template:\n", "\n", "{reqs}\"\"\"\n", + "\n", + "# Function to get the messages for the prompt\n", + "# Will only get messages AFTER the tool call\n", "def get_prompt_messages(messages):\n", " tool_call = None\n", " other_msgs = []\n", @@ -59,10 +147,40 @@ " elif tool_call is not None:\n", " other_msgs.append(m)\n", " return [SystemMessage(content=prompt_system.format(reqs=tool_call))] + other_msgs\n", - " \n", - "\n", - "prompt_gen_chain = get_prompt_messages | llm\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3293d2d3-e060-4c98-8065-3696b049b5fc", + "metadata": {}, + "outputs": [], + "source": [ + "prompt_gen_chain = get_prompt_messages | llm" + ] + }, + { + "cell_type": "markdown", + "id": "8dbabda8-34f0-4eef-bce2-ad3ff505366b", + "metadata": {}, + "source": [ + "## Define the state logic\n", "\n", + "This is the logic for what state the chatbot is in.\n", + "If the last message is a tool call, then we are in the state where the \"prompt creator\" (`prompt`) should respond.\n", + "Otherwise, if the last message is not a HumanMessage, then we know the human should respond next and so we are in the `END` state.\n", + "If the last message is a HumanMessage, then if there was a tool call previously we are in the `prompt` state.\n", + "Otherwise, we are in the \"info gathering\" (`info`) state." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "74f29e15-20e2-420c-a450-84e929f16e4e", + "metadata": {}, + "outputs": [], + "source": [ "def get_state(messages):\n", " if _is_tool_call(messages[-1]):\n", " return \"prompt\"\n", @@ -74,6 +192,17 @@ " return \"info\"" ] }, + { + "cell_type": "markdown", + "id": "b76bea78-07a5-418f-9b7c-71c376d4b6f7", + "metadata": {}, + "source": [ + "## Create the graph\n", + "\n", + "We can now the create the graph.\n", + "We will use a SqliteSaver to persist conversation history." + ] + }, { "cell_type": "code", "execution_count": 2, @@ -96,9 +225,19 @@ "graph = workflow.compile(checkpointer=memory)" ] }, + { + "cell_type": "markdown", + "id": "afcf523c-265d-45cf-a981-fc50c50c1738", + "metadata": {}, + "source": [ + "## Use the graph\n", + "\n", + "We can now use the created chatbot." + ] + }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "25793988-45a2-4e65-b33c-64e72aadb10e", "metadata": {}, "outputs": [ @@ -240,6 +379,20 @@ "---\n", "\n" ] + }, + { + "name": "stdin", + "output_type": "stream", + "text": [ + "User (q/Q to quit): q\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "AI: Byebye\n" + ] } ], "source": [