From 0e878ccc2d92a91e10038eb2d5acbc2f795e016c Mon Sep 17 00:00:00 2001 From: Myeongseop Kim <81076998+amicus-veritatis@users.noreply.github.com> Date: Thu, 6 Jul 2023 22:21:03 +0900 Subject: [PATCH] Add HumanInputChatModel (#7256) - Description: This is a chat model equivalent of HumanInputLLM. An example notebook is also added. - Tag maintainer: @hwchase17, @baskaryan - Twitter handle: N/A --- .../chat/how_to/human_input_chat_model.ipynb | 212 ++++++++++++++++++ langchain/chat_models/__init__.py | 2 + langchain/chat_models/human.py | 122 ++++++++++ 3 files changed, 336 insertions(+) create mode 100644 docs/extras/modules/model_io/models/chat/how_to/human_input_chat_model.ipynb create mode 100644 langchain/chat_models/human.py diff --git a/docs/extras/modules/model_io/models/chat/how_to/human_input_chat_model.ipynb b/docs/extras/modules/model_io/models/chat/how_to/human_input_chat_model.ipynb new file mode 100644 index 0000000000..3b5ce27713 --- /dev/null +++ b/docs/extras/modules/model_io/models/chat/how_to/human_input_chat_model.ipynb @@ -0,0 +1,212 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Human input Chat Model\n", + "\n", + "Along with HumanInputLLM, LangChain also provides a pseudo Chat Model class that can be used for testing, debugging, or educational purposes. This allows you to mock out calls to the Chat Model and simulate how a human would respond if they received the messages.\n", + "\n", + "In this notebook, we go over how to use this.\n", + "\n", + "We start this with using the HumanInputChatModel in an agent." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.chat_models.human import HumanInputChatModel" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Since we will use the `WikipediaQueryRun` tool in this notebook, you might need to install the `wikipedia` package if you haven't done so already." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/Users/mskim58/dev/research/chatbot/github/langchain/.venv/bin/python: No module named pip\n", + "Note: you may need to restart the kernel to use updated packages.\n" + ] + } + ], + "source": [ + "%pip install wikipedia" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.agents import load_tools\n", + "from langchain.agents import initialize_agent\n", + "from langchain.agents import AgentType" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "tools = load_tools([\"wikipedia\"])\n", + "llm = HumanInputChatModel()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "agent = initialize_agent(\n", + " tools, llm, agent=AgentType.CHAT_ZERO_SHOT_REACT_DESCRIPTION, verbose=True\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "\n", + "\u001b[1m> Entering new chain...\u001b[0m\n", + "\n", + " ======= start of message ======= \n", + "\n", + "\n", + "type: system\n", + "data:\n", + " content: \"Answer the following questions as best you can. You have access to the following tools:\\n\\nWikipedia: A wrapper around Wikipedia. Useful for when you need to answer general questions about people, places, companies, facts, historical events, or other subjects. Input should be a search query.\\n\\nThe way you use the tools is by specifying a json blob.\\nSpecifically, this json should have a `action` key (with the name of the tool to use) and a `action_input` key (with the input to the tool going here).\\n\\nThe only values that should be in the \\\"action\\\" field are: Wikipedia\\n\\nThe $JSON_BLOB should only contain a SINGLE action, do NOT return a list of multiple actions. Here is an example of a valid $JSON_BLOB:\\n\\n```\\n{\\n \\\"action\\\": $TOOL_NAME,\\n \\\"action_input\\\": $INPUT\\n}\\n```\\n\\nALWAYS use the following format:\\n\\nQuestion: the input question you must answer\\nThought: you should always think about what to do\\nAction:\\n```\\n$JSON_BLOB\\n```\\nObservation: the result of the action\\n... (this Thought/Action/Observation can repeat N times)\\nThought: I now know the final answer\\nFinal Answer: the final answer to the original input question\\n\\nBegin! Reminder to always use the exact characters `Final Answer` when responding.\"\n", + " additional_kwargs: {}\n", + "\n", + "======= end of message ======= \n", + "\n", + "\n", + "\n", + " ======= start of message ======= \n", + "\n", + "\n", + "type: human\n", + "data:\n", + " content: 'What is Bocchi the Rock?\n", + "\n", + "\n", + " '\n", + " additional_kwargs: {}\n", + " example: false\n", + "\n", + "======= end of message ======= \n", + "\n", + "\n", + "\u001b[32;1m\u001b[1;3mAction:\n", + "```\n", + "{\n", + " \"action\": \"Wikipedia\",\n", + " \"action_input\": \"What is Bocchi the Rock?\"\n", + "}\n", + "```\u001b[0m\n", + "Observation: \u001b[36;1m\u001b[1;3mPage: Bocchi the Rock!\n", + "Summary: Bocchi the Rock! (ぼっち・ざ・ろっく!, Botchi Za Rokku!) is a Japanese four-panel manga series written and illustrated by Aki Hamaji. It has been serialized in Houbunsha's seinen manga magazine Manga Time Kirara Max since December 2017. Its chapters have been collected in five tankōbon volumes as of November 2022.\n", + "An anime television series adaptation produced by CloverWorks aired from October to December 2022. The series has been praised for its writing, comedy, characters, and depiction of social anxiety, with the anime's visual creativity receiving acclaim.\n", + "\n", + "Page: Hitori Bocchi no Marumaru Seikatsu\n", + "Summary: Hitori Bocchi no Marumaru Seikatsu (Japanese: ひとりぼっちの○○生活, lit. \"Bocchi Hitori's ____ Life\" or \"The ____ Life of Being Alone\") is a Japanese yonkoma manga series written and illustrated by Katsuwo. It was serialized in ASCII Media Works' Comic Dengeki Daioh \"g\" magazine from September 2013 to April 2021. Eight tankōbon volumes have been released. An anime television series adaptation by C2C aired from April to June 2019.\n", + "\n", + "Page: Kessoku Band (album)\n", + "Summary: Kessoku Band (Japanese: 結束バンド, Hepburn: Kessoku Bando) is the debut studio album by Kessoku Band, a fictional musical group from the anime television series Bocchi the Rock!, released digitally on December 25, 2022, and physically on CD on December 28 by Aniplex. Featuring vocals from voice actresses Yoshino Aoyama, Sayumi Suzushiro, Saku Mizuno, and Ikumi Hasegawa, the album consists of 14 tracks previously heard in the anime, including a cover of Asian Kung-Fu Generation's \"Rockn' Roll, Morning Light Falls on You\", as well as newly recorded songs; nine singles preceded the album's physical release. Commercially, Kessoku Band peaked at number one on the Billboard Japan Hot Albums Chart and Oricon Albums Chart, and was certified gold by the Recording Industry Association of Japan.\n", + "\n", + "\u001b[0m\n", + "Thought:\n", + " ======= start of message ======= \n", + "\n", + "\n", + "type: system\n", + "data:\n", + " content: \"Answer the following questions as best you can. You have access to the following tools:\\n\\nWikipedia: A wrapper around Wikipedia. Useful for when you need to answer general questions about people, places, companies, facts, historical events, or other subjects. Input should be a search query.\\n\\nThe way you use the tools is by specifying a json blob.\\nSpecifically, this json should have a `action` key (with the name of the tool to use) and a `action_input` key (with the input to the tool going here).\\n\\nThe only values that should be in the \\\"action\\\" field are: Wikipedia\\n\\nThe $JSON_BLOB should only contain a SINGLE action, do NOT return a list of multiple actions. Here is an example of a valid $JSON_BLOB:\\n\\n```\\n{\\n \\\"action\\\": $TOOL_NAME,\\n \\\"action_input\\\": $INPUT\\n}\\n```\\n\\nALWAYS use the following format:\\n\\nQuestion: the input question you must answer\\nThought: you should always think about what to do\\nAction:\\n```\\n$JSON_BLOB\\n```\\nObservation: the result of the action\\n... (this Thought/Action/Observation can repeat N times)\\nThought: I now know the final answer\\nFinal Answer: the final answer to the original input question\\n\\nBegin! Reminder to always use the exact characters `Final Answer` when responding.\"\n", + " additional_kwargs: {}\n", + "\n", + "======= end of message ======= \n", + "\n", + "\n", + "\n", + " ======= start of message ======= \n", + "\n", + "\n", + "type: human\n", + "data:\n", + " content: \"What is Bocchi the Rock?\\n\\nThis was your previous work (but I haven't seen any of it! I only see what you return as final answer):\\nAction:\\n```\\n{\\n \\\"action\\\": \\\"Wikipedia\\\",\\n \\\"action_input\\\": \\\"What is Bocchi the Rock?\\\"\\n}\\n```\\nObservation: Page: Bocchi the Rock!\\nSummary: Bocchi the Rock! (ぼっち・ざ・ろっく!, Botchi Za Rokku!) is a Japanese four-panel manga series written and illustrated by Aki Hamaji. It has been serialized in Houbunsha's seinen manga magazine Manga Time Kirara Max since December 2017. Its chapters have been collected in five tankōbon volumes as of November 2022.\\nAn anime television series adaptation produced by CloverWorks aired from October to December 2022. The series has been praised for its writing, comedy, characters, and depiction of social anxiety, with the anime's visual creativity receiving acclaim.\\n\\nPage: Hitori Bocchi no Marumaru Seikatsu\\nSummary: Hitori Bocchi no Marumaru Seikatsu (Japanese: ひとりぼっちの○○生活, lit. \\\"Bocchi Hitori's ____ Life\\\" or \\\"The ____ Life of Being Alone\\\") is a Japanese yonkoma manga series written and illustrated by Katsuwo. It was serialized in ASCII Media Works' Comic Dengeki Daioh \\\"g\\\" magazine from September 2013 to April 2021. Eight tankōbon volumes have been released. An anime television series adaptation by C2C aired from April to June 2019.\\n\\nPage: Kessoku Band (album)\\nSummary: Kessoku Band (Japanese: 結束バンド, Hepburn: Kessoku Bando) is the debut studio album by Kessoku Band, a fictional musical group from the anime television series Bocchi the Rock!, released digitally on December 25, 2022, and physically on CD on December 28 by Aniplex. Featuring vocals from voice actresses Yoshino Aoyama, Sayumi Suzushiro, Saku Mizuno, and Ikumi Hasegawa, the album consists of 14 tracks previously heard in the anime, including a cover of Asian Kung-Fu Generation's \\\"Rockn' Roll, Morning Light Falls on You\\\", as well as newly recorded songs; nine singles preceded the album's physical release. Commercially, Kessoku Band peaked at number one on the Billboard Japan Hot Albums Chart and Oricon Albums Chart, and was certified gold by the Recording Industry Association of Japan.\\n\\n\\nThought:\"\n", + " additional_kwargs: {}\n", + " example: false\n", + "\n", + "======= end of message ======= \n", + "\n", + "\n", + "\u001b[32;1m\u001b[1;3mThis finally works.\n", + "Final Answer: Bocchi the Rock! is a four-panel manga series and anime television series. The series has been praised for its writing, comedy, characters, and depiction of social anxiety, with the anime's visual creativity receiving acclaim.\u001b[0m\n", + "\n", + "\u001b[1m> Finished chain.\u001b[0m\n" + ] + }, + { + "data": { + "text/plain": [ + "{'input': 'What is Bocchi the Rock?',\n", + " 'output': \"Bocchi the Rock! is a four-panel manga series and anime television series. The series has been praised for its writing, comedy, characters, and depiction of social anxiety, with the anime's visual creativity receiving acclaim.\"}" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "agent(\"What is Bocchi the Rock?\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.10.9" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/langchain/chat_models/__init__.py b/langchain/chat_models/__init__.py index 39952027df..8f02497bd8 100644 --- a/langchain/chat_models/__init__.py +++ b/langchain/chat_models/__init__.py @@ -2,6 +2,7 @@ from langchain.chat_models.anthropic import ChatAnthropic from langchain.chat_models.azure_openai import AzureChatOpenAI from langchain.chat_models.fake import FakeListChatModel from langchain.chat_models.google_palm import ChatGooglePalm +from langchain.chat_models.human import HumanInputChatModel from langchain.chat_models.openai import ChatOpenAI from langchain.chat_models.promptlayer_openai import PromptLayerChatOpenAI from langchain.chat_models.vertexai import ChatVertexAI @@ -14,4 +15,5 @@ __all__ = [ "ChatAnthropic", "ChatGooglePalm", "ChatVertexAI", + "HumanInputChatModel", ] diff --git a/langchain/chat_models/human.py b/langchain/chat_models/human.py new file mode 100644 index 0000000000..80721aacac --- /dev/null +++ b/langchain/chat_models/human.py @@ -0,0 +1,122 @@ +"""ChatModel wrapper which returns user input as the response..""" +import asyncio +from functools import partial +from io import StringIO +from typing import Any, Callable, List, Mapping, Optional + +import yaml +from pydantic import Field + +from langchain.callbacks.manager import ( + AsyncCallbackManagerForLLMRun, + CallbackManagerForLLMRun, +) +from langchain.chat_models.base import BaseChatModel +from langchain.llms.utils import enforce_stop_tokens +from langchain.schema.messages import ( + BaseMessage, + HumanMessage, + _message_from_dict, + messages_to_dict, +) +from langchain.schema.output import ChatGeneration, ChatResult + + +def _display_messages(messages: List[BaseMessage]) -> None: + dict_messages = messages_to_dict(messages) + for message in dict_messages: + yaml_string = yaml.dump( + message, + default_flow_style=False, + sort_keys=False, + allow_unicode=True, + width=10000, + line_break=None, + ) + print("\n", "======= start of message =======", "\n\n") + print(yaml_string) + print("======= end of message =======", "\n\n") + + +def _collect_yaml_input( + messages: List[BaseMessage], stop: Optional[List[str]] = None +) -> BaseMessage: + """Collects and returns user input as a single string.""" + lines = [] + while True: + line = input() + if not line.strip(): + break + if stop and any(seq in line for seq in stop): + break + lines.append(line) + yaml_string = "\n".join(lines) + + # Try to parse the input string as YAML + try: + message = _message_from_dict(yaml.safe_load(StringIO(yaml_string))) + if message is None: + return HumanMessage(content="") + if stop: + message.content = enforce_stop_tokens(message.content, stop) + return message + except yaml.YAMLError: + raise ValueError("Invalid YAML string entered.") + except ValueError: + raise ValueError("Invalid message entered.") + + +class HumanInputChatModel(BaseChatModel): + """ChatModel wrapper which returns user input as the response..""" + + input_func: Callable = Field(default_factory=lambda: _collect_yaml_input) + message_func: Callable = Field(default_factory=lambda: _display_messages) + separator: str = "\n" + input_kwargs: Mapping[str, Any] = {} + message_kwargs: Mapping[str, Any] = {} + + @property + def _identifying_params(self) -> Mapping[str, Any]: + return { + "input_func": self.input_func.__name__, + "message_func": self.message_func.__name__, + } + + @property + def _llm_type(self) -> str: + """Returns the type of LLM.""" + return "human-input-chat-model" + + def _generate( + self, + messages: List[BaseMessage], + stop: Optional[List[str]] = None, + run_manager: Optional[CallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> ChatResult: + """ + Displays the messages to the user and returns their input as a response. + + Args: + messages (List[BaseMessage]): The messages to be displayed to the user. + stop (Optional[List[str]]): A list of stop strings. + run_manager (Optional[CallbackManagerForLLMRun]): Currently not used. + + Returns: + ChatResult: The user's input as a response. + """ + self.message_func(messages, **self.message_kwargs) + user_input = self.input_func(messages, stop=stop, **self.input_kwargs) + return ChatResult(generations=[ChatGeneration(message=user_input)]) + + async def _agenerate( + self, + messages: List[BaseMessage], + stop: Optional[List[str]] = None, + run_manager: Optional[AsyncCallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> ChatResult: + func = partial( + self._generate, messages, stop=stop, run_manager=run_manager, **kwargs + ) + return await asyncio.get_event_loop().run_in_executor(None, func)