feat: added custom conversation chains and prompts
This commit is contained in:
parent
fd55da902a
commit
67bc082569
9 changed files with 193 additions and 35 deletions
|
|
@ -3,6 +3,9 @@ chains:
|
|||
- LLMMathChain
|
||||
- LLMCheckerChain
|
||||
- ConversationChain
|
||||
- SeriesCharacterChain
|
||||
- MidJourneyPromptChain
|
||||
- TimeTravelGuideChain
|
||||
|
||||
agents:
|
||||
- ZeroShotAgent
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
100
src/backend/langflow/interface/chains/custom.py
Normal file
100
src/backend/langflow/interface/chains/custom.py
Normal 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,
|
||||
}
|
||||
|
|
@ -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}")
|
||||
|
|
|
|||
|
|
@ -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]:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue