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
- LLMCheckerChain
- ConversationChain
- SeriesCharacterChain
- MidJourneyPromptChain
- TimeTravelGuideChain
agents:
- ZeroShotAgent

View file

@ -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)

View file

@ -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
]

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.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}")

View file

@ -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]:

View file

@ -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)

View file

@ -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)

View file

@ -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 (