feat: added custom conversation chains and prompts

This commit is contained in:
Gabriel Almeida 2023-04-04 11:52:13 -03:00
commit 67bc082569
9 changed files with 193 additions and 35 deletions

View file

@ -3,6 +3,9 @@ chains:
- LLMMathChain - LLMMathChain
- LLMCheckerChain - LLMCheckerChain
- ConversationChain - ConversationChain
- SeriesCharacterChain
- MidJourneyPromptChain
- TimeTravelGuideChain
agents: agents:
- ZeroShotAgent - ZeroShotAgent

View file

@ -75,7 +75,9 @@ class PromptNode(Node):
for param in prompt_params: for param in prompt_params:
prompt_text = self.params[param] prompt_text = self.params[param]
variables = extract_input_variables_from_prompt(prompt_text) variables = extract_input_variables_from_prompt(prompt_text)
self.params["input_variables"].extend(variables) self.params["input_variables"].extend(variables)
self.params["input_variables"] = list(set(self.params["input_variables"]))
self._build() self._build()
return deepcopy(self._built_object) return deepcopy(self._built_object)

View file

@ -1,4 +1,5 @@
from typing import Dict, List, Optional from typing import Dict, List, Optional
from langflow.custom.customs import get_custom_nodes
from langflow.interface.base import LangChainTypeCreator from langflow.interface.base import LangChainTypeCreator
from langflow.interface.custom_lists import chain_type_to_cls_dict 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: def type_to_loader_dict(self) -> Dict:
if self.type_dict is None: if self.type_dict is None:
self.type_dict = chain_type_to_cls_dict 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 return self.type_dict
def get_signature(self, name: str) -> Optional[Dict]: def get_signature(self, name: str) -> Optional[Dict]:
try: 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: except ValueError as exc:
raise ValueError("Memory not found") from exc raise ValueError("Chain not found") from exc
def to_list(self) -> List[str]: 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 [ return [
chain.__name__ chain
for chain in self.type_to_loader_dict.values() for chain in default_chains + custom_chains
if chain.__name__ in settings.chains or settings.dev if chain in settings.chains or settings.dev
] ]

View file

@ -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,
}

View file

@ -10,6 +10,7 @@ from langchain.chat_models.base import BaseChatModel
from langchain.llms.base import BaseLLM from langchain.llms.base import BaseLLM
from langchain.tools import BaseTool from langchain.tools import BaseTool
from langflow.interface.tools.util import get_tool_by_name 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: def import_prompt(prompt: str) -> PromptTemplate:
from langflow.interface.prompts.custom import CUSTOM_PROMPTS
"""Import prompt from prompt name""" """Import prompt from prompt name"""
if prompt == "ZeroShotPrompt": if prompt == "ZeroShotPrompt":
return import_class("langchain.prompts.PromptTemplate") return import_class("langchain.prompts.PromptTemplate")
elif prompt in CUSTOM_PROMPTS:
return CUSTOM_PROMPTS[prompt]
return import_class(f"langchain.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: def import_chain(chain: str) -> Chain:
"""Import chain from chain name""" """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}") return import_class(f"langchain.chains.{chain}")

View file

@ -26,6 +26,10 @@ class PromptCreator(LangChainTypeCreator):
for prompt_name in prompts.__all__ for prompt_name in prompts.__all__
if not prompt_name.islower() and prompt_name in settings.prompts 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 return self.type_dict
def get_signature(self, name: str) -> Optional[Dict]: def get_signature(self, name: str) -> Optional[Dict]:

View file

@ -1,4 +1,4 @@
from typing import List, Optional from typing import Dict, List, Optional
from langchain.prompts import PromptTemplate from langchain.prompts import PromptTemplate
from pydantic import root_validator 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.base import Template, TemplateField
from langflow.template.nodes import PromptTemplateNode 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}. # Steps to create a BaseCustomPrompt:
You must know all of the knowledge of {character}.""" # 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): class BaseCustomPrompt(PromptTemplate):
template: str = "" template: str = ""
description: Optional[str] description: Optional[str]
human_text: str = "\n {input}" ai_prefix: Optional[str]
@root_validator(pre=False) @root_validator(pre=False)
def build_template(cls, values): def build_template(cls, values):
format_dict = {} format_dict = {}
ai_prefix_format_dict = {}
for key in values.get("input_variables", []): for key in values.get("input_variables", []):
new_value = values[key] new_value = values.get(key, f"{{{key}}}")
format_dict[key] = new_value 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"].format(**format_dict)
values["template"] = values["template"] + values["human_text"] values["template"] = values["template"]
values["input_variables"] = extract_input_variables_from_prompt( values["input_variables"] = extract_input_variables_from_prompt(
values["template"] values["template"]
) )
return values 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): class SeriesCharacterPrompt(BaseCustomPrompt):
# Add a very descriptive description for the prompt generator # 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." ] = "A prompt that asks the AI to act like a character from a series."
character: str character: str
series: str series: str
human_text: str = "\n {input}" template: str = """I want you to act like {character} from {series}.
template: str = CHARACTER_PROMPT 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"] input_variables: List[str] = ["character", "series"]
CUSTOM_PROMPTS = {"SeriesCharacterPrompt": SeriesCharacterPrompt}
if __name__ == "__main__": if __name__ == "__main__":
prompt = SeriesCharacterPrompt(character="Walter White", series="Breaking Bad") prompt = SeriesCharacterPrompt(character="Harry Potter", series="Harry Potter")
user_input = "I am the one who knocks" print(prompt.template)
full_prompt = prompt.format(input=user_input)
print(full_prompt)

View file

@ -52,6 +52,12 @@ def process_graph(data_graph: Dict[str, Any]):
) )
logger.debug("Loaded langchain object") 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 # Generate result and thought
logger.debug("Generating result and thought") logger.debug("Generating result and thought")
result, thought = get_result_and_thought_using_graph(langchain_object, message) 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 loaded_langchain.verbose = True
with io.StringIO() as output_buffer, contextlib.redirect_stdout(output_buffer): with io.StringIO() as output_buffer, contextlib.redirect_stdout(output_buffer):
chat_input = None 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: for key in loaded_langchain.input_keys:
if key == "chat_history" and hasattr(loaded_langchain, "memory"): if key != memory_key:
loaded_langchain.memory.memory_key = "chat_history"
else:
chat_input = {key: message} chat_input = {key: message}
if hasattr(loaded_langchain, "return_intermediate_steps"): if hasattr(loaded_langchain, "return_intermediate_steps"):
# https://github.com/hwchase17/langchain/issues/2068 # https://github.com/hwchase17/langchain/issues/2068
loaded_langchain.return_intermediate_steps = False 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: try:
output = loaded_langchain(chat_input) output = function_to_call(chat_input)
except ValueError as exc: except ValueError as exc:
logger.debug("Error: %s", str(exc)) logger.debug("Error: %s", str(exc))
output = loaded_langchain.run(chat_input) output = loaded_langchain.run(chat_input)

View file

@ -251,7 +251,7 @@ class PromptFrontendNode(FrontendNode):
field.field_type = "str" field.field_type = "str"
field.multiline = True field.multiline = True
field.value = HUMAN_PROMPT if "Human" in field.name else SYSTEM_PROMPT 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 field.value = DEFAULT_PROMPT
if ( if (