diff --git a/src/backend/langflow/config.yaml b/src/backend/langflow/config.yaml index 85130c9a0..25ad70839 100644 --- a/src/backend/langflow/config.yaml +++ b/src/backend/langflow/config.yaml @@ -3,6 +3,9 @@ chains: - LLMMathChain - LLMCheckerChain - ConversationChain + - SeriesCharacterChain + - MidJourneyPromptChain + - TimeTravelGuideChain agents: - ZeroShotAgent diff --git a/src/backend/langflow/graph/nodes.py b/src/backend/langflow/graph/nodes.py index b465e3817..df109d3a8 100644 --- a/src/backend/langflow/graph/nodes.py +++ b/src/backend/langflow/graph/nodes.py @@ -75,7 +75,9 @@ class PromptNode(Node): for param in prompt_params: prompt_text = self.params[param] variables = extract_input_variables_from_prompt(prompt_text) + self.params["input_variables"].extend(variables) + self.params["input_variables"] = list(set(self.params["input_variables"])) self._build() return deepcopy(self._built_object) diff --git a/src/backend/langflow/interface/chains/base.py b/src/backend/langflow/interface/chains/base.py index 36542b7d4..45a9b2ddb 100644 --- a/src/backend/langflow/interface/chains/base.py +++ b/src/backend/langflow/interface/chains/base.py @@ -1,4 +1,5 @@ from typing import Dict, List, Optional +from langflow.custom.customs import get_custom_nodes from langflow.interface.base import LangChainTypeCreator from langflow.interface.custom_lists import chain_type_to_cls_dict @@ -15,19 +16,27 @@ class ChainCreator(LangChainTypeCreator): def type_to_loader_dict(self) -> Dict: if self.type_dict is None: self.type_dict = chain_type_to_cls_dict + from langflow.interface.chains.custom import CUSTOM_CHAINS + + self.type_dict.update(CUSTOM_CHAINS) return self.type_dict def get_signature(self, name: str) -> Optional[Dict]: try: - return build_template_from_class(name, chain_type_to_cls_dict) + if name in get_custom_nodes(self.type_name).keys(): + return get_custom_nodes(self.type_name)[name] + return build_template_from_class(name, self.type_to_loader_dict) except ValueError as exc: - raise ValueError("Memory not found") from exc + raise ValueError("Chain not found") from exc def to_list(self) -> List[str]: + custom_chains = list(get_custom_nodes("chains").keys()) + default_chains = list(self.type_to_loader_dict.keys()) + # Check if the chain is in the settings return [ - chain.__name__ - for chain in self.type_to_loader_dict.values() - if chain.__name__ in settings.chains or settings.dev + chain + for chain in default_chains + custom_chains + if chain in settings.chains or settings.dev ] diff --git a/src/backend/langflow/interface/chains/custom.py b/src/backend/langflow/interface/chains/custom.py new file mode 100644 index 000000000..07e08699f --- /dev/null +++ b/src/backend/langflow/interface/chains/custom.py @@ -0,0 +1,100 @@ +from typing import Optional +from langchain.chains import ConversationChain +from langflow.graph.utils import extract_input_variables_from_prompt +from pydantic import root_validator, Field +from langchain.memory.buffer import ConversationBufferMemory +from langchain.schema import BaseMemory + + +DEFAULT_SUFFIX = """" +Current conversation: +{history} +Human: {input} +{ai_prefix}""" + + +class BaseCustomChain(ConversationChain): + """BaseCustomChain is a chain you can use to have a conversation with a custom character.""" + + template: Optional[str] + + ai_prefix_key: Optional[str] + """Field to use as the ai_prefix. It needs to be set and has to be in the template""" + + @root_validator(pre=False) + def build_template(cls, values): + format_dict = {} + input_variables = extract_input_variables_from_prompt(values["template"]) + + if values.get("ai_prefix_key", None) is None: + values["ai_prefix_key"] = values["memory"].ai_prefix + + for key in input_variables: + new_value = values.get(key, f"{{{key}}}") + format_dict[key] = new_value + if key == values.get("ai_prefix_key", None): + values["memory"].ai_prefix = new_value + + values["template"] = values["template"].format(**format_dict) + + values["template"] = values["template"] + values["input_variables"] = extract_input_variables_from_prompt( + values["template"] + ) + values["prompt"].template = values["template"] + values["prompt"].input_variables = values["input_variables"] + return values + + +class SeriesCharacterChain(BaseCustomChain): + """SeriesCharacterChain is a chain you can use to have a conversation with a character from a series.""" + + character: str + series: str + template: Optional[ + str + ] = """I want you to act like {character} from {series}. +I want you to respond and answer like {character}. do not write any explanations. only answer like {character}. +You must know all of the knowledge of {character}. +Current conversation: +{history} +Human: {input} +{character}:""" + memory: BaseMemory = Field(default_factory=ConversationBufferMemory) + ai_prefix_key: Optional[str] = "character" + """Default memory store.""" + + +class MidJourneyPromptChain(BaseCustomChain): + """MidJourneyPromptChain is a chain you can use to generate new MidJourney prompts.""" + + template: Optional[ + str + ] = """I want you to act as a prompt generator for Midjourney's artificial intelligence program. + Your job is to provide detailed and creative descriptions that will inspire unique and interesting images from the AI. + Keep in mind that the AI is capable of understanding a wide range of language and can interpret abstract concepts, so feel free to be as imaginative and descriptive as possible. + For example, you could describe a scene from a futuristic city, or a surreal landscape filled with strange creatures. + The more detailed and imaginative your description, the more interesting the resulting image will be. Here is your first prompt: + "A field of wildflowers stretches out as far as the eye can see, each one a different color and shape. In the distance, a massive tree towers over the landscape, its branches reaching up to the sky like tentacles.\" + + Current conversation: + {history} + Human: {input} + AI:""" + + +class TimeTravelGuideChain(BaseCustomChain): + template: Optional[ + str + ] = """I want you to act as my time travel guide. You are helpful and creative. I will provide you with the historical period or future time I want to visit and you will suggest the best events, sights, or people to experience. Provide the suggestions and any necessary information. + Current conversation: + {history} + Human: {input} + AI:""" + + +CUSTOM_CHAINS = { + "SeriesCharacterChain": SeriesCharacterChain, + "MidJourneyPromptChain": MidJourneyPromptChain, + "TimeTravelGuideChain": TimeTravelGuideChain, +} diff --git a/src/backend/langflow/interface/importing/utils.py b/src/backend/langflow/interface/importing/utils.py index 0ada410e4..af4631ed2 100644 --- a/src/backend/langflow/interface/importing/utils.py +++ b/src/backend/langflow/interface/importing/utils.py @@ -10,6 +10,7 @@ from langchain.chat_models.base import BaseChatModel from langchain.llms.base import BaseLLM from langchain.tools import BaseTool + from langflow.interface.tools.util import get_tool_by_name @@ -66,9 +67,13 @@ def import_class(class_path: str) -> Any: def import_prompt(prompt: str) -> PromptTemplate: + from langflow.interface.prompts.custom import CUSTOM_PROMPTS + """Import prompt from prompt name""" if prompt == "ZeroShotPrompt": return import_class("langchain.prompts.PromptTemplate") + elif prompt in CUSTOM_PROMPTS: + return CUSTOM_PROMPTS[prompt] return import_class(f"langchain.prompts.{prompt}") @@ -102,4 +107,8 @@ def import_tool(tool: str) -> BaseTool: def import_chain(chain: str) -> Chain: """Import chain from chain name""" + from langflow.interface.chains.custom import CUSTOM_CHAINS + + if chain in CUSTOM_CHAINS: + return CUSTOM_CHAINS[chain] return import_class(f"langchain.chains.{chain}") diff --git a/src/backend/langflow/interface/prompts/base.py b/src/backend/langflow/interface/prompts/base.py index a7de7a611..ad289c531 100644 --- a/src/backend/langflow/interface/prompts/base.py +++ b/src/backend/langflow/interface/prompts/base.py @@ -26,6 +26,10 @@ class PromptCreator(LangChainTypeCreator): for prompt_name in prompts.__all__ if not prompt_name.islower() and prompt_name in settings.prompts } + # Merge CUSTOM_PROMPTS into self.type_dict + from langflow.interface.prompts.custom import CUSTOM_PROMPTS + + self.type_dict.update(CUSTOM_PROMPTS) return self.type_dict def get_signature(self, name: str) -> Optional[Dict]: diff --git a/src/backend/langflow/interface/prompts/custom.py b/src/backend/langflow/interface/prompts/custom.py index d1bb98c62..295316fce 100644 --- a/src/backend/langflow/interface/prompts/custom.py +++ b/src/backend/langflow/interface/prompts/custom.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import Dict, List, Optional from langchain.prompts import PromptTemplate from pydantic import root_validator @@ -7,43 +7,49 @@ from langflow.graph.utils import extract_input_variables_from_prompt from langflow.template.base import Template, TemplateField from langflow.template.nodes import PromptTemplateNode -CHARACTER_PROMPT = """I want you to act like {character} from {series}. -I want you to respond and answer like {character}. do not write any explanations. only answer like {character}. -You must know all of the knowledge of {character}.""" + +# Steps to create a BaseCustomPrompt: +# 1. Create a prompt template that endes with: +# Current conversation: +# {history} +# Human: {input} +# {ai_prefix}: +# 2. Create a class that inherits from BaseCustomPrompt +# 3. Add the following class attributes: +# template: str = "" +# description: Optional[str] +# ai_prefix: Optional[str] = "{ai_prefix}" +# 3.1. The ai_prefix should be a value in input_variables +# SeriesCharacterPrompt is a working example +# If used in a LLMChain, with a Memory module, it will work as expected +# We should consider creating ConversationalChains that expose custom parameters +# That way it will be easier to create custom prompts class BaseCustomPrompt(PromptTemplate): template: str = "" description: Optional[str] - human_text: str = "\n {input}" + ai_prefix: Optional[str] @root_validator(pre=False) def build_template(cls, values): format_dict = {} + ai_prefix_format_dict = {} for key in values.get("input_variables", []): - new_value = values[key] + new_value = values.get(key, f"{{{key}}}") format_dict[key] = new_value + if key in values["ai_prefix"]: + ai_prefix_format_dict[key] = new_value + values["ai_prefix"] = values["ai_prefix"].format(**ai_prefix_format_dict) values["template"] = values["template"].format(**format_dict) - values["template"] = values["template"] + values["human_text"] + values["template"] = values["template"] values["input_variables"] = extract_input_variables_from_prompt( values["template"] ) return values - def build_frontend_node(self) -> PromptTemplateNode: - return PromptTemplateNode( - template=Template( - type_name="test", - fields=[ - TemplateField(name=field, field_type="str", required=True) - for field in self.input_variables - ], - ), - description=self.description or "", - ) - class SeriesCharacterPrompt(BaseCustomPrompt): # Add a very descriptive description for the prompt generator @@ -52,14 +58,21 @@ class SeriesCharacterPrompt(BaseCustomPrompt): ] = "A prompt that asks the AI to act like a character from a series." character: str series: str - human_text: str = "\n {input}" - template: str = CHARACTER_PROMPT + template: str = """I want you to act like {character} from {series}. +I want you to respond and answer like {character}. do not write any explanations. only answer like {character}. +You must know all of the knowledge of {character}. +Current conversation: +{history} +Human: {input} +{character}:""" + + ai_prefix: str = "{character}" input_variables: List[str] = ["character", "series"] +CUSTOM_PROMPTS = {"SeriesCharacterPrompt": SeriesCharacterPrompt} + if __name__ == "__main__": - prompt = SeriesCharacterPrompt(character="Walter White", series="Breaking Bad") - user_input = "I am the one who knocks" - full_prompt = prompt.format(input=user_input) - print(full_prompt) + prompt = SeriesCharacterPrompt(character="Harry Potter", series="Harry Potter") + print(prompt.template) diff --git a/src/backend/langflow/interface/run.py b/src/backend/langflow/interface/run.py index 0da273722..9e8aaa841 100644 --- a/src/backend/langflow/interface/run.py +++ b/src/backend/langflow/interface/run.py @@ -52,6 +52,12 @@ def process_graph(data_graph: Dict[str, Any]): ) logger.debug("Loaded langchain object") + if langchain_object is None: + # Raise user facing error + raise ValueError( + "There was an error loading the flow. Please, check all the nodes and try again." + ) + # Generate result and thought logger.debug("Generating result and thought") result, thought = get_result_and_thought_using_graph(langchain_object, message) @@ -73,18 +79,30 @@ def get_result_and_thought_using_graph(loaded_langchain, message: str): loaded_langchain.verbose = True with io.StringIO() as output_buffer, contextlib.redirect_stdout(output_buffer): chat_input = None + memory_key = "" + if hasattr(loaded_langchain, "memory"): + mem_vars = loaded_langchain.memory.memory_variables + memory_key = mem_vars[0] if mem_vars else "" + for key in loaded_langchain.input_keys: - if key == "chat_history" and hasattr(loaded_langchain, "memory"): - loaded_langchain.memory.memory_key = "chat_history" - else: + if key != memory_key: chat_input = {key: message} if hasattr(loaded_langchain, "return_intermediate_steps"): # https://github.com/hwchase17/langchain/issues/2068 loaded_langchain.return_intermediate_steps = False + # I'm not sure about this yet. + function_to_call = None + if hasattr(loaded_langchain, "memory"): + elif hasattr(loaded_langchain, "run"): + function_to_call = loaded_langchain.run + function_to_call = loaded_langchain.predict + else: + function_to_call = loaded_langchain + try: - output = loaded_langchain(chat_input) + output = function_to_call(chat_input) except ValueError as exc: logger.debug("Error: %s", str(exc)) output = loaded_langchain.run(chat_input) diff --git a/src/backend/langflow/template/nodes.py b/src/backend/langflow/template/nodes.py index b28a38842..6be772483 100644 --- a/src/backend/langflow/template/nodes.py +++ b/src/backend/langflow/template/nodes.py @@ -251,7 +251,7 @@ class PromptFrontendNode(FrontendNode): field.field_type = "str" field.multiline = True field.value = HUMAN_PROMPT if "Human" in field.name else SYSTEM_PROMPT - if field.name == "template": + if field.name == "template" and field.value == "": field.value = DEFAULT_PROMPT if (